Top
Best
New

Posted by ingve 10 hours ago

Building a Shell(healeycodes.com)
139 points | 31 comments
lvales 8 hours ago|
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...

chubot 3 hours ago||
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)

healeycodes 8 hours ago|||
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.

ferguess_k 5 hours ago||
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.
wei03288 5 hours ago||
The pipe section is the part that changes how you think about processes. Once you've manually done the dup2 dance — close write-end in parent, close read-end in child, wire them up — it stops being magic and starts being obvious why `grep | sort | uniq` works at all. The thing that surprised me building a similar toy was how late in the process job control has to come: you can get a working pipe chain surprisingly fast, and then job control (SIGTSTP, tcsetpgrp, the whole mess) costs 5x more than everything else combined.
chubot 3 hours ago||
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

sloum 54 minutes ago||
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.
emersion 7 hours ago||
Some time ago I've written an article about a particular aspect of shells, job control: https://emersion.fr/blog/2019/job-control/
rrampage 2 hours ago||
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...

mzs 8 hours ago||
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.
ratzkewatzke 3 hours ago||
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.
lasgawe 2 hours ago||
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.
lioeters 4 hours ago||
Link was previously posted by author: https://news.ycombinator.com/item?id=47398749 There are other good quality articles on their site, and maybe deserves the imaginary points.
hexer303 5 hours ago||
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.
doe88 2 hours ago|
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.
giancarlostoro 2 hours ago||
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...

epr 2 hours ago||
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.

More comments...