logo

drewdevault.com

[mirror] blog and personal website of Drew DeVault git clone https://hacktivis.me/git/mirror/drewdevault.com.git
commit: f5a8ea92ab3e340afa86a2c0c9201c7a30790089
parent b0b934f33636d053ceb40dc611f74576f14c6137
Author: Drew DeVault <sir@cmpwn.com>
Date:   Tue, 18 Apr 2023 11:07:06 +0200

rc

Diffstat:

Acontent/blog/2023-04-18-A-new-shell-for-Unix.md212+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 212 insertions(+), 0 deletions(-)

diff --git a/content/blog/2023-04-18-A-new-shell-for-Unix.md b/content/blog/2023-04-18-A-new-shell-for-Unix.md @@ -0,0 +1,212 @@ +--- +title: "rc: a new shell for Unix" +date: 2023-04-18 +--- + +[rc] is a Unix shell I've been working on over the past couple of weeks, though +it's been in the design stages for a while longer than that. It's not done or +ready for general use yet, but it is interesting, so let's talk about it. + +[rc]: https://git.sr.ht/~sircmpwn/rc + +As the name (which is subject to change) implies, rc is inspired by the Plan 9 +[rc][plan9] shell. It's not an implementation of Plan 9 rc, however: it departs +in many notable ways. I'll assume most readers are more familiar with POSIX +shell or Bash and skip many of the direct comparisons to Plan 9. Also, though +most of the features work as described, the shell is a work-in-progress and some +of the design I'm going over today has not been implemented yet. + +[plan9]: http://man.9front.org/1/rc + +Let's start with the basics. Simple usage works much as you'd expect: + +``` +name=ddevault +echo Hello $name +``` + +But there's already something important that might catch your eye here: the lack +of quotes around $name. One substantial improvement rc makes over POSIX shells +and Bash right off the bat is fixing our global shell quoting nightmare. There's +no need to quote variables! + +``` +# POSIX shell +x="hello world" +printf '%s\n' $x +# hello +# world + +# rc +x="hello world" +printf '%s\n' $x +# hello world +``` + +Of course, the POSIX behavior is actually useful sometimes. rc provides for this +by acknowledging that shells have not just one fundamental type (strings), but +two: strings and *lists* of strings, i.e. argument vectors. + +``` +x=(one two three) +echo $x(1) # prints first item ("one") +echo $x # expands to arguments (echo "one" "two" "three") +echo $#x # length operator: prints 3 + +x="echo hello world" +$x +# echo hello world: command not found + +x=(echo hello world) +$x +# hello world + +# expands to a string, list values separated with space: +$"x +# echo hello world: command not found +``` + +You can also slice up lists and get a subset of items: + +``` +x=(one two three four five) +echo $x(-4) # one two three four +echo $x(2-) # two three four five +echo $x(2-4) # two three +``` + +A departure from Plan 9 rc is that the list operators can be used with strings +for string operations as well: + +``` +x="hello world" +echo $#x # 11 +echo $x(2) # e +echo $x(1-5) # hello +``` + +rc also supports loops. The simple case is iterating over the command line +arguments: + +``` +% cat test.rc +for (arg) { + echo $arg +} +% rc test.rc one two three +one +two +three +``` + +{ } is a command like any other; this can be simplified to for (arg) echo +$arg. You can also enumerate any list with in: + +``` +list=(one two three) +for (item in $list) { + echo $item +} +``` + +We also have while loops and if: + +``` +while (true) { + if (test $x -eq 10) { + echo ten + } else { + echo $x + } +} +``` + +Functions are defined like so: + +``` +fn greet { + echo Hello $1 +} + +greet ddevaut +``` + +Again, any command can be used, so this can be simplified to fn greet echo $1. +You can also add named parameters: + +``` +fn greet(user time) { + echo Hello $user + echo It is $time +} + +greet ddevaut `{date} +``` + +Note the use of `{script...} instead of $() for command expansion. Additional +arguments are still placed in $*, allowing for the user to combine +variadic-style functions with named arguments. + +Here's a more complex script that I run to perform sanity checks before applying +patches: + +``` +#!/bin/rc +fn check_branch(branch) { + if (test `{git rev-parse --abbrev-ref HEAD} != $branch) { + echo "Error: not on master branch" + exit 1 + } +} + +fn check_uncommitted { + if (test `{git status -suno | wc -l} -ne 0) { + echo "Error: you have uncommitted changes" + exit 1 + } +} + +fn check_behind { + if (test `{git rev-list "@{u}.." | wc -l} -ne 0) { + echo "Error: your branch is behind upstream" + exit 1 + } +} + +check_branch master +check_uncommitted +check_behind +exec git pull +``` + +That's a brief introduction to rc! Presently it clocks in at about 2500 lines of +Hare. It's not done yet, so don't get too excited, but much of what's described +here is already working. Some other stuff which works but I didn't mention +include: + +- Boolean compound commands (x && y, x || y) +- Pipelines, which can pipe arbitrary file descriptors ("x |[2] y") +- Redirects, also including arbitrary fds ("x >[2=1] file") + +It also has a [formal context-free grammar][grammar], which is a +work-in-progress but speaks to our desire to have a robust description of the +shell available for users and other implementations. We use Ember Sawady's +excellent [madeline][made] for our interactive mode, which supports command line +editing, history, ^r, and fish-style forward completion OOTB. + +[grammar]: https://git.sr.ht/~sircmpwn/rc/tree/master/item/doc/grammar.txt +[made]: https://git.d2evs.net/~ecs/madeline/ + +Future plans include: + +- Simple arithmetic expansion +- Named pipe expansions +- Sub-shells +- switch statements +- Port to [ares](https://ares-os.org) +- Find a new name, perhaps + +It needs a small amount of polish, cleanup, and bugs fixed as well. + +I hope you find it interesting! I will let you know when it's done. Feel free +to [play with it][rc] in the meanwhile, and maybe send some patches?