alt.hn

3/17/2026 at 9:52:34 AM

Building a Shell

https://healeycodes.com/building-a-shell

by ingve

3/17/2026 at 11:27:15 AM

Building a shell is a great exercise, but honestly having to deal with string parsing is such a bother that it robs like 2/3 of the joy along the way. I once built a very simple one in Go [0] as a learning exercise and I stopped once I started getting frustrated with all the corner cases.

[0] https://github.com/lourencovales/codecrafters/blob/master/sh...

by lvales

3/17/2026 at 4:15:33 PM

A common problem I noticed is that if you took certain courses in computer science, you may have a pre-conceived notion of how to parse programming languages, and the shell language doesn't quite fit that model

I have seen this misconception many times

In Oils, we have some pretty minor elaborations of the standard model, and it makes things a lot easier

How to Parse Shell Like a Programming Language - https://www.oilshell.org/blog/2019/02/07.html

Everything I wrote there still holds, although that post could use some minor updates (and OSH is the most bash-compatible shell, and more POSIX-compatible than /bin/sh on Debian - e.g. https://pages.oils.pub/spec-compat/2025-11-02/renamed-tmp/sp... )

---

To summarize that, I'd say that doing as much work as possible in the lexer, with regular languages and "lexer modes", drastically reduces the complexity of writing a shell parser

And it's not just one parser -- shell actually has 5 to 15 different parsers, depending on how you count

I often show this file to make that point: https://oils.pub/release/0.37.0/pub/src-tree.wwz/_gen/_tmp/m...

(linked from https://oils.pub/release/0.37.0/quality.html)

Fine-grained heterogenous algebraic data types also help. Shells in C tend to use a homogeneous command* and word* kind of representation

https://oils.pub/release/0.37.0/pub/src-tree.wwz/frontend/sy... (~700 lines of type definitions)

by chubot

3/17/2026 at 11:31:42 AM

Author here, and yeah, I agree. I skipped writing a parser altogether and just split on whitespace and `|` so that I could get to the interesting bits.

For side-projects, I have to ask myself if I'm writing a parser, or if I'm building something else; e.g. for a toy programming language, it's way more fun to start with an AST and play around, and come back to the parser if you really fall in love with it.

by healeycodes

3/17/2026 at 2:11:57 PM

Can say the same for control characters in terminals. I even think maybe it's just easier to ditch them all and use QT to build a "terminal" with clickable urls, something similar to what TempleOS does.

by ferguess_k

3/17/2026 at 5:29:09 PM

Fun read! I built a minimal Linux shell [0] in c and Zig last year which does not depend on libc. It was a great way to learn about execve, the new-ish clone3 syscall and how Linux starts a process. Parsing strings is the least fun part of the building the shell.

[0] https://gist.github.com/rrampage/5046b60ca2d040bcffb49ee38e8...

by rrampage

3/17/2026 at 11:39:15 AM

Had an assignment to build a shell in a week, how hard could it be?

  controlling terminal
  session leader
  job control
The parser was easy in comparison.

by mzs

3/17/2026 at 4:31:52 PM

There's a very good exercise on Codecrafters (https://app.codecrafters.io/courses/shell/overview) to walk you through writing your own shell. I found it enlightening, as well as a good way to learn a new language.

by ratzkewatzke

3/17/2026 at 2:34:14 PM

Unix shells are conceptually simple but hide a surprising amount of complexity under the hood that we take for granted. I recently had build my own PTY controller. There were so many edge-cases to deal with. It took weeks of stress testing and writing many tests to get it right.

by hexer303

3/17/2026 at 11:17:20 AM

Bit of pedantry but I don't think traditional unix shell (like this) follows repl model; the shell is not usually doing printing of the result of evaluation. Instead the printing happens more as a side effect of the commands.

by zokier

3/17/2026 at 12:58:55 PM

It’s a shell, not the whole thing. The whole thing is the shell+kernel+programs.

by skydhash

3/17/2026 at 9:59:59 PM

Even if you view the system as a whole the printing is deeply intertwined with the evaluation, which is very different from repl where eval returns a value and print prints it

by zokier

3/17/2026 at 12:12:45 PM

I remember my first shell programming I ever did was batch in windows back in the 3.11/95 days.

The first line was always to turn off echo, and I've always wondered why that was a decision for batch script. Or I'm misremembering. 30 years of separation makes it hard to remember the details.

by jermaustin1

3/17/2026 at 1:23:51 PM

Echo in that case prints command lines before executing them. Its analog is `set -x` rather than `echo`.

by enoint

3/18/2026 at 6:40:24 AM

> the shell is not usually doing printing of the result of evaluation

I always include $? in the prompt, so I guess I can say it does print the result of the evaluation.

by teo_zero

3/17/2026 at 7:12:56 PM

It prints a prompt.

by themafia

3/17/2026 at 9:45:04 PM

That's not what print in repl means.

by zokier

3/17/2026 at 5:58:04 PM

Great article. There are many things every developer should do when starting to learn programming or when trying to improve their skills. This is one of them. I once built a shell-like programming language (not an interpreter). If anyone reading this wants to improve their skills, I strongly suggest building your own shell from scratch.

by lasgawe

3/17/2026 at 5:33:32 PM

Is there a (real) shell whose code is relatively short and self contained and would be valuable to read? This was always something I wanted to do but never quite spent time to look for a good one to explore.

by doe88

3/17/2026 at 9:07:46 PM

It depends on what you are looking for. My recommendation for learning "how is X done in a shell" is the OpenBSD ksh: https://github.com/ibara/oksh

It's what they use for /bin/sh, it has everything that a complete shell needs (including a mechanism for providing command completions) and has code that is much easier to read than bash or zsh.

Something that I also would recommend is the design document for the plan9 rc shell; it is a worthwhile read for anybody interested in shells: https://doc.cat-v.org/plan_9/4th_edition/papers/rc

An implementation is also available if one wants to look at how it could be done: https://github.com/rakitzis/rc

by tame3902

3/17/2026 at 5:37:10 PM

Although not the same... Destroy All Software has videos on building your own shell using Ruby. I watched it to learn and it was a lot of fun to watch him basically building a shell, I'm not really a Ruby guy, but it was easy to grasp. It's not free, you would need a subscription, but its worth the watch otherwise.

https://www.destroyallsoftware.com/screencasts/catalog/shell...

by giancarlostoro

3/17/2026 at 9:09:39 PM

I don't know how real you want -- those criteria are probably self contradictory :-)

Marc Rochkind's book Avanced UNIX Programming implemented a basic shell, through iterations. You can see the first at e.g. here https://github.com/gmarler/AUPv2/blob/master/c5/sh0.c

It might be a bit old too. The book is very good but again, quite old. There seem to be free copies of it on the net.

BTW, does anyone know if Marc Rochkind is alive? His site basepath.com seems to be for sale :-(

by emmelaich

3/17/2026 at 5:52:21 PM

I think there's a good one if you search around for "xv6 sh.c". Hard to tell immediately from a google search just now since there are many implementations (people do it in school) and github's currently blocking requests from my phone.

Also helpful may be running strace on your shell, then reviewing the output line by line to make sure you understand each. This is a VERY instructive exercise to do in general.

by epr

3/17/2026 at 8:40:17 PM

https://st.suckless.org/

by willx86

3/17/2026 at 9:46:55 PM

St is a terminal, not a shell.

by vidarh

3/17/2026 at 12:36:39 PM

Fun read. Wonder if you are able to edit text in the shell, or if you need to implement a gap buffer to allow it?

by austy69

3/17/2026 at 1:08:15 PM

Editing the current line works because I brought in https://man7.org/linux/man-pages/man3/readline.3.html towards the end so I could support editing, tab completion, and history.

IIRC readline uses a `char *` internally since the length of a user-edited line is fairly bounded.

by healeycodes

3/17/2026 at 1:33:21 PM

Very cool. Currently working on the beginning of a small text editor so this part seemed interesting and was curious of any overlap. Thanks for the interesting post!

by austy69

3/17/2026 at 6:51:55 PM

worth noting that you get basic line editing for "free" from kernels tty subsystem even if you don't use readline.

by zokier

3/18/2026 at 11:23:23 AM

Yes, but it is really basic. Is it more than backspace? Most cursor key presses are just forwarded to the program as escape sequences.

by 1718627440

3/17/2026 at 2:40:36 PM

Interesting. I wanted to do toast | bash to let the AI drive the computer but the bash shell really got in the way. Too much complexity. The things that annoy humans, $ expansion, special characters, etc don't work for AI either. Ended up writing a custom shell for AI (and humans). When a tool gets in the way, sometimes it just time to change the tool.

by dirk94018

3/17/2026 at 12:05:37 PM

[flagged]

by rigorclaw

3/17/2026 at 2:25:16 PM

[dead]

by wei03288

3/17/2026 at 4:08:54 PM

Yup, job control is a huge mess. I think Bill Joy was able to modify the shell, the syscall interface, and the terminal driver at the same time to implement the hacky mechanism of job control. But a few years later that kind of crosscutting change would have been harder

One thing we learned from implementing job control in https://oils.pub is that the differing pipeline semantics of bash and zsh makes a difference

In bash, the last part of the pipeline is forked (unless shopt -s lastpipe)

In zsh, it isn't

    $ bash -c 'echo hi | read x; echo $x'  # no output
          
    $ zsh -c 'echo hi | read x; echo $x'
    hi
And then that affects this case:

    bash$ sleep 5 | read
    ^Z
    [1]+  Stopped                 sleep 5 | read


    zsh$ sleep 5 | read    # job control doesn't apply to this case in zsh
    ^Zzsh: job can't be suspended

So yeah the semantics of shell are not very well specified (which is one reason for OSH and YSH). I recall a bug running an Alpine Linux shell script where this difference matters -- if the last part is NOT forked, then the script doesn't run

I think there was almost a "double bug" -- the script relied on the `read` output being "lost", even though that was likely not the intended behavior

by chubot

3/17/2026 at 10:38:37 PM

FWIW here is another piece of trivia about job control: the API means you can't spawn a child process "safely" in POSIX -- you have to trust that that the executable you're spawning is well-behaved (or use more advanced Linux process isolation)

In this case it was the Zed editor spawning the zsh shell:

How to Lose Control of your Shell - https://registerspill.thorstenball.com/p/how-to-lose-control...

zsh has a bug where it doesn't do the job control cleanup properly in some cases -- when fork-exec() optimizations happen.

This can mess up the calling process. For example, here you could no longer kill Zed by hitting Ctrl-C, even after zsh is done.

My comment: https://lobste.rs/s/hru0ib/how_lose_control_your_shell#c_dfl...

by chubot

3/17/2026 at 7:07:42 PM

Yes!! This!! I wrote a shell awhile back and was pretty happy with it... but could _not_ get job control to work quite right. It was a big pain.

by sloum

3/17/2026 at 12:05:22 PM

[dead]

by leontloveless

3/17/2026 at 5:49:30 PM

[dead]

by stainlu

3/18/2026 at 6:59:34 PM

[dead]

by Heer_J

3/17/2026 at 11:33:38 AM

[flagged]

by hristian

3/17/2026 at 11:53:35 AM

Somebody blamed this comment on LLMs, and maybe/probably it is, but I think the first sentence is spot-on so I thought it was worth replying to.

Dealing with the corner cases ends up teaching you a lot about a language and for an ancient language like the shell, dealing with the corner cases also takes you through the thinking process of the original authors and the constraints they were subject to. I found myself in this situation while writing EndBASIC and wrote an article with the surprises I encountered, because I found the journey fascinating: https://www.endbasic.dev/2023/01/endbasic-parsing-difficulti...

by jmmv

3/17/2026 at 11:39:52 AM

Not sure it tells all that much about 'how the OS works'. This is a historical abstraction that happened to look how it looks today with all its numerous warts and shortcomings.

We can easily imagine it done a better way - for all the criticism of Windows, PowerShell gives a glimpse into this hypothetical future.

by gf000

3/17/2026 at 11:39:20 AM

Fascinating that you resurrected an account from 2014 just for LLM spam, were the credentials compromised or something?

by Retr0id

3/17/2026 at 12:00:15 PM

Maybe the author had it logged into something that their claw had access to

by IncreasePosts