DEV Community

Jeff Melton
Jeff Melton

Posted on

I keep changing shells. It's not restlessness — it's requirements drift.

I keep switching shells. From the outside it looks like restlessness, but each change came from the same kind of moment: my work shifted, and the shell that was right yesterday became the wrong tool today. This is the tour — what each shell taught me, and what eventually pushed me to the next one.

PowerShell

PowerShell was the first shell I used that wasn't bash. I came to it for Windows admin tasks and stayed for the structured-data pipeline. After a career of awk | sed | cut to chop apart text, the realization that I could pass objects down a pipeline — with named fields, types, even parallel streams — was a genuine brain reorganization. Jeff Snover's baby has legs, even outside the Windows world. The verb-noun command construction is intuitive, easy to remember and search for, and produces readable (if verbose) pipelines.

I wasn't doing Windows admin all the time, though, and when I changed jobs to a primarily macOS-and-Linux desk, pwsh started feeling like overkill for the day-to-day.

Fish

Fish came next, once I started at Sam's Club and was back on Linux full-time. Fish's inline suggestions and syntax highlighting are chef's-kiss good, and out of the box it's fast to load — until you start sourcing serious config. The community's partial Rust rewrite is solid work and I'm curious where it lands.

What pushed me away wasn't Fish itself. When I moved to Walmart's Kubernetes platform team, I was suddenly drowning in JSON and CSV from kubectl, vSphere, and a dozen other tools — and I missed how trivial PowerShell made that kind of work. Fish is still passing bytes down the pipe, and at that volume the bytes-to-objects context switch in my head started costing real time. Going back to PowerShell wasn't an option either; its parallelism story wasn't written for Walmart scale.

Nushell

Nushell clicked immediately because of the PowerShell muscle memory. Unpacking a value from a huge nested JSON object (I'm looking at you, vSphere) is trivial, and the underlying architecture handles parallelism at scale without breaking a sweat or pinning every CPU core. It's the fastest I've ever been at the interactive pipeline.

Nushell is written in Rust and iterates fast — including, sometimes, right through your config. When one update left me with a pile of broken tools I'd have to rewrite, I rage-quit. Spending a day and a half reworking config and scripts wipes out a lot of interactive-use time saved.

Elvish

Elvish was where I went after the rage-quit. With no config it's the second-fastest shell I've benchmarked across all eleven I've tried; with my full ~9KB config loaded, there's zero measurable impact on startup time. That's black magic. Its parallel operations beat Nushell for speed, and I have a hard time matching them even with compiled Go or Rust binaries.

But the syntax fights me on complex pipelines. It takes me longer to assemble something non-trivial in Elvish than in Nushell, and that wall-time loss eats the cold-start win. I respect Elvish enormously; it's not the shell I reach for.

Xonsh

Xonsh was a tempting experiment: a shell where Python is a first-class citizen, not just a thing you call out to. The entire Python ecosystem one import away from your prompt is, on paper, an enormous amount of leverage.

In practice, the GIL is a hard ceiling. The thing that makes Python Python — single-threaded execution for everything that isn't explicitly released — also means xonsh inherits the same constraint at the shell layer. Parallel pipelines aren't actually parallel; they're cooperative at best. For interactive work this can be fine; for any pipeline doing real concurrent work, it's a non-starter. I'm not sure there's a fix that doesn't involve abandoning the thing that made xonsh appealing in the first place.

Startup is the other constraint. With no config, xonsh is the slowest of the eleven shells I've benchmarked. With a heavy config full of custom function definitions — the kind of config I used to lean on — you can hit 4-second init times. Ask me how I know.

Xonsh's load times, combined with the agent-written-code revolution, are mostly what pushed me to move nearly everything out of my shell rc and into compiled binaries. But that's a story for a different post.

Murex

Murex I picked up a couple of years ago. Also Go-based, also fast to ship features. It has innovative ideas you don't find elsewhere — typed pipelines, structured tests as a first-class shell concept, real attention to error semantics — and a strong backward-compatibility commitment to its small user base, which after the Nushell experience meant a lot.

The shape of the language reminds me of Perl. I mean that as a compliment first: a pragmatic, mid-level abstraction density, willing to be terse where terseness pays off and verbose where clarity matters, with a healthy disregard for academic purity. There's also more than one way to do most things in Murex — which Perl die-hards have always loved and everyone else recognizes as a real cognitive-load tax. Some of Murex's syntax is intuitive once you see it; some isn't, in the same way some Perl isn't.

The deal-breaker for me is that Murex has no parallelism at the prompt — and the architecture won't allow it to be retrofitted. It uses goroutines heavily internally, but the prompt itself is sequential. For my workload that's a non-starter.

Where I am now

I'm back on Nushell full-time, and I know I'm on a clock — sooner or later there will be another breaking change, and I'll lose another day and a half. I'm curious by nature (and more than a little ADHD), so I keep watching for new contenders.

I've also started scoping out my own.

Top comments (0)