Rc shell quick reference

Cheat sheet for the rc(1) shell from plan9/unix.

Paper: http://doc.cat-v.org/plan_9/4th_edition/papers/rc

Manual: http://man.cat-v.org/9front/1/rc

Ports

Unix port with readline/editline, etc: https://github.com/rakitzis/rc

9base standalone port: https://github.com/kfarwell/9base-rc

Rfork

A large number of rc scripts on plan9 will prefix their logic with calls to rfork(1).

In particular, you'll usually see

rfork n

which forks the process namespace, so cd(1), etc. don't affect the calling shell process.

From rc(1):

rfork [nNeEsfFm]
	Become a new process group using rfork(flags) where
	flags is composed of the bitwise OR of the rfork flags
	specified by the option letters (see fork(2)). If no
	flags are given, they default to ens.  The flags and
	their meanings are: n is RFNAMEG; N is RFCNAMEG; e is
	RFENVG; E is RFCENVG; s is RFNOTEG; f is RFFDG; F is
	RFCFDG; and m is RFNOMNT.

Import

Import state (functions, variables, etc.) from another file:

. /path/to/file

Here documents

This streams over an fd (default: 0) a block, substituting variables deliniated by the token EOF:

ed $3 <<EOF
g/$1/s//$2/g
w
EOF

Escaping is performed as per the paper:

To include a literal $ in a here document, type $$.

If the name of a variable is followed immediately by ^, the caret is deleted.

Variable substitution can be entirely suppressed by enclosing the EOF marker following << in quotation marks, as in <<’EOF’.

Fd redirection

Redirections are executed from left to right. (from paper)

Here documents

# Stream to cmd's fd 4
cmd <<[4]End
...
End

Pipe one fd

# Pipe only fd 2 to and from fd 2
vc junk.c |[2] grep -v ’^$’

Connecting fd on a pipe

# Creates a pipeline with cmd1’s file descriptor 5 connected through a pipe to cmd2’s file descriptor 19 (from paper)
cmd1 |[5=19] cmd2

Redirect one fd

# Redirect only fd 2
vc junk.c >[2] junk.diag

Close an fd

# Close only fd 2
vc junk.c >[2=]

Conjoin fd and redirect

# Redirect both fd 1 and 2 into ;junk.out'
vc junk.c >junk.out >[2=1]

Usage

Showing the name called in execution argv[0]-style:

argv0 = $0
fn usage {
	echo >[1=2] 'usage:' $argv0 'foo ...'
	exit 'usage'
}

Commandline flags

Process all commandline flags (-n 10) and consume arguments:

somenumber = ()	# Empty initialization
while(~ $1 -*)
	switch($1){
	case -n
		# Shift from '-n' token to the argument token
		shift
		somenumber = $1
		# Shift from value to the next $* token
		shift
	case -*
		# Unknown flags trigger usage text
		usage
	}

# $* now contains all non-flag-related arguments
# So in 'foo -n 5 bar' $* would now contain 'bar'

Switching on number of arguments

Switch on the number of commandline arguments, can be useful after processing flags:

switch($#*){
case 0
	arg=$DEFAULT
	if(~ $#weather 1)
		arg=$weather
case 1
	arg=$1
case *
	usage
}

If you don't want to switch, a nice pattern may be:

# If there's not only 1 argument in $*
if(! ~ $#* 1){
	usage
}

Snowcone operator

sort <{for(f in $files){
	g '/re/' $f
}} | uniq

Divergence in rakitzis vs plan9

The way if statements work diverges from the plan9 style in rakitzis's rc.

Rakitzis

Note how the else is on the same line as the curly brace.

if(test -e $new){
	if(! ~ $new $f){
		echo >[2=1] fail: $new exists, not renaming $f
	}
}else{
	mv $f $new
	echo $f → $new
}

Plan9

Note how the if and if-not-if exist on newlines.

for(i){
	if(test -f $i) go $i
	if not if(test -f /bin/$i) go /bin/$i
	if not if(test -f /bin/*/$i) go /bin/*/$i
	if not if(test -f /bin/*/*/$i) go /bin/*/*/$i
	if not echo 'src: can''t find '$i
}

Simplified:

if(~ $#remotesys 1)
	suf = @$remotesys
if not
	remotesys=''