I’m a big fan of the Plan 9 shell, rc
(originally built for 10th Edition Research Unix, but mostly known now from the Plan 9 port).
It takes the good parts of the Bourne shell, but cleaned up:
- C-like syntax, rather than Algol-like.
if (cond) cmd
rather thanif cond; then cmd; fi
, and more{}
blocks. - Sane backtick syntax:
`{prog args}
rather than`prog args`
, which is hard to nest, or$(prog args)
, which works, but looks like a Makefile variable!/li> -
Better quoting syntax. Single quotes only; no interpolations. The only character that needs to be escaped inside quotes is
'
, and it can be escaped just by doubling it (Basic-style):''
. The best alternative I’ve found in Bourne shell is to use double quotes, which potentially require a lot more escapes, or the very ugly'\''
to end the quote, insert an escaped quote, and resume it.Instead of interpolation, there’s an explicit concatenation operator
^
available, as in'some string with '^$foo^' included'
. I normally really like string interpolation in programming languages, but there’s something nice about concatenation in a radically simple syntax. - Variables store lists internally, rather than strings. That means variables don’t get re-split when you use them, which eliminates a bunch of your quoting of variables, and of course, the quoting bugs when you get it wrong.
Here’s my current T
script, mostly for testing Perl code:
if (~ $#* 0) {
echo put | 9p write acme/$winid/ctl
*=$%
}
for (file in $*) {
switch ($file) {
case *.tex
pdflatex $file
case *greeter/*.t
test_file=`{echo $file | sed 's,.*/(t/.*),\1,'}
cd `{echo $file | sed 's,/t/.*,,'} &&
vagrant up &&
echo 'cd /vagrant && prove -l '^$"test_file^'; exit' | vagrant ssh web
case *greeter/*.pm
pkg=`{pkg $file}
cd `{echo $file | sed 's,/lib/.*,,'} &&
test_file=`{find t -name '*.t' | xargs rg -l '(^|[^:\w])'^$pkg^'([^:\w]|$)'}
vagrant up &&
echo 'cd /vagrant && prove -l '^$"test_file^'; exit' | vagrant ssh web
case *DBIx-Class-Relationship-Abbreviate/*.t
test_file=`{echo $file | sed 's,.*/(t/.*),\1,'}
cd `{echo $file | sed 's,/t/.*,,'} &&
sh -c 'perlbrew use perl-5.26.1@abbreviate && prove -l "$test_file"'
case *DBIx-Class-Relationship-Abbreviate/*.pm
pkg=`{pkg $file}
cd `{echo $file | sed 's,/lib/.*,,'} &&
test_file=`{find t -name '*.t' | xargs rg -l '(^|[^:\w])'^$pkg^'([^:\w]|$)'}
sh -c 'perlbrew use perl-5.26.1@abbreviate && prove -l "$test_file"'
}
}
and a Bash translation:
if [ "$#" -eq 0 ]; then
echo put | 9p write acme/$winid/ctl
set $samfile
fi
for file in "$@"; do
case $file in
*.tex)
pdflatex "$file"
;;
*greeter/*.t)
test_file=`echo "$file" | sed 's,.*/(t/.*),\1,'`
cd `echo "$file" | sed 's,/t/.*,,'` &&
vagrant up &&
echo "cd /vagrant && prove -l $test_file; exit" | vagrant ssh web
;;
*greeter/*.pm)
pkg=`pkg "$file"`
cd `echo "$file" | sed 's,/lib/.*,,'` &&
test_file=`find t -name '*.t' | xargs rg -l '(^|[^:\w])'"$pkg"'([^:\w]|$)'`
vagrant up &&
echo "cd /vagrant && prove -l $test_file; exit" | vagrant ssh web
;;
*DBIx-Class-Relationship-Abbreviate/*.t)
test_file=`echo "$file" | sed 's,.*/(t/.*),\1,'`
cd `echo "$file" | sed 's,/t/.*,,'` &&
(perlbrew use perl-5.26.1@abbreviate && prove -l "$test_file")
;;
*DBIx-Class-Relationship-Abbreviate/*.pm)
pkg=`pkg "$file"`
cd `echo "$file" | sed 's,/lib/.*,,'` &&
test_file=`find t -name '*.t' | xargs rg -l '(^|[^:\w])'"$pkg"'([^:\w]|$)'`
(perlbrew use perl-5.26.1@abbreviate && prove -l "$test_file"')
;;
esac
done
~
is an rc
built-in for doing glob matching. $*
, like in sh
, is the current program’s arguments. (Not a lot of people know that, since sh
re-splits it whenever you use it, so it isn’t terribly useful.) Putting a #
in front of a variable name gives you the length of the list, so $#*
is the number of arguments to the current script.
This script can be run from the command-line, giving it the name of a file to test, but I normally run it without arguments from the Acme tag. The if
statement detects that case, saves the current file for me, and then sets the script’s arguments to the name of the current file, which Acme has exported as $%
:
*=$%
The ability to assign to $*
and the availability of $%
are both bits of syntactic orthogonality that rc
adds over sh
.
The for
syntax is more C/Perl-like, which I think is nice. The switch
statement also looks better to my eye; for some reason, sh
decides this is the point where it needs to start looking like line noise. No accounting for taste, I guess.
The rest of the should be pretty self-explanatory, or at least off-topic for this blog post; I’ll just point out two things:
rc
doesn’t need the ugly"$file"
defensive quoting everywhere; and- For whatever reason,
perlbrew
doesn’t supportrc
, so we do have to delegate back tosh
to use it. It’s still worth it to simplify the rest of the script.