Skip to main content
Back to posts

🐚 I wanted a shell that could keep my old habits

niklas-heer/quirl

A well-stirred shell — Bash-familiar commands, typed data pipelines, and a Lua extension SDK, folded into one fast Rust binary.

1 0 Rust MIT Updated 2 days ago

I used zsh for a long time. I tried Fish and wandered through a few other shells, as one does when the actual task is insufficiently distracting.

Then I found Nushell and made it the default in my terminals.

Working with data in it was wonderful. CSV and JSON stopped being things I had to take apart with increasingly elaborate text processing. I could look at rows, pick fields, filter values and keep drilling into the result. Even a directory listing was something I could ask questions of.

Then I copied a command from my notes, and it didn’t work.

The clipboard kept sending me back

The same thing happened with commands from the internet and from sessions with AI tools. They all assumed the usual shell syntax. My new default shell had different rules.

Those rules were part of what made the data side so good. But I kept switching back to a traditional shell just to run whatever I had copied, and I didn’t want every paste to turn into a compatibility negotiation.

I had also spent some time with Xonsh. Having a real programming language inside the shell appealed to me. For scripts and customization I would rather use a language with a life beyond configuring my prompt.

Eventually the wish list was clear enough:

  • Keep a familiar mode for everyday commands and Bash/zsh habits.
  • Switch to a data mode when I actually want to work with data.
  • Script and extend the shell with a general-purpose language.

Quirl is my attempt to put those three in one place.

One session, both modes

Familiar commands, typed data, and Lua in one Quirl session. Recorded with a development build of Quirl’s 0.1 line. A byte pipeline runs first, then a JSON file becomes filtered records, followed by an explicit Bash block and a Lua expression. The sample services are local demo data.

The first command is deliberately boring:

Terminal window
printf 'api\nworker\njobs\n' | wc -l && echo 'pipeline: healthy'

A pipe, a count, and a command that runs if the pipeline succeeds. That is the point. Not every trip to the terminal should require a new worldview.

Quirl’s normal mode covers a defined core of shell behavior. It is not a complete Bash or zsh, so dialect-specific syntax gets an explicit home:

Terminal window
bash { cat <(printf 'Bash still works.\n') }

There are zsh blocks too. Existing habits stay usable, and it is always clear which language is interpreting a command.

Data mode

Type mode data, and a command like this works with the fields in a JSON file:

open services.json | where latency > 50 | select service latency

In the recording, the result is the services whose latency is above 50. No counting columns, no guessing which run of spaces belongs to the table formatting.

This is the part of the structured-shell experience I wanted to keep. Exploring data should feel like exploring data. CSV files, JSON documents and structured listings are where it earns its keep.

Type mode normal to go back.

Lua for scripting

For the extension language I ended up with Lua rather than Python.

I wanted something fast and embeddable, and Lua is both. Quirl already uses it for configuration, scripts and extensions. Rust handles the shell’s core; the embedded Lua runtime runs with explicit resource limits.

You can try it without writing a configuration file:

lua return { answer = 6 * 7, language = _VERSION }

A tiny example, but it shows the boundary. When I want to program the shell, there is a programming language available. I don’t have to stretch a configuration format into one.

Quirl means whisk. I wanted to mix familiar commands, structured data and an extension language into a shell I would enjoy using. The 0.1 release is an early version of that for Linux and macOS.

Mostly, I would like to spend less time explaining my clipboard to my terminal.