RC(1plan9)

πŸ’– NAME πŸ’–

okay so this is rc, the Plan 9 shell, and also the whole little squad of built-ins that live on the same page: rc, cd, eval, exec, exit, flag, rfork, shift, wait, whatis, ., ~. It's giving command language. periodt. 🫢


✨ SYNOPSIS ✨

rc [ -srdiIlxepvV ] [ -c command ] [ file [ arg ... ]]

πŸ¦‹ DESCRIPTION πŸ¦‹

Rc is the Plan 9 shell, bestie. It executes command lines that it reads from a terminal or from a file β€” or, if you pass it the -c flag, straight from rc's own argument list. 🌈

πŸ’… Command Lines πŸ’…

A command line is a sequence of commands, separated by ampersands or semicolons (& or ;), and terminated by a newline. The commands are executed in sequence, left to right, no cap.

Here's the tea though: rc does not wait for a command followed by & to finish executing before it starts the following command. Whenever a command followed by & is executed, its process id is assigned to the rc variable $apid. And whenever a command not followed by & exits or is terminated, the rc variable $status gets the process's wait message (see wait(3)); it'll be the null string if the command was successful. ✨

A long command line may be continued on subsequent lines by typing a backslash (\) followed by a newline. That sequence is treated as though it were a blank. Backslash isn't special in any other way, iykyk.

A number-sign (#) and any following characters up to (but not including) the next newline are ignored β€” except in quotation marks. πŸ™ˆ

🌸 Simple Commands 🌸

A simple command is a sequence of arguments interspersed with I/O redirections.

  • If the first argument is the name of an rc function or of one of rc's built-in commands, rc executes it itself. πŸ‘‘
  • Otherwise, if the name starts with a slash (/), it must be the path name of the program to be executed.
  • Names containing no initial slash are searched for in a list of directory names stored in $path. The first executable file of the given name found in a directory in $path is the program that gets executed.

To be executable, the user must have execute permission (see stat(3)) and the file must be either an executable binary for the current machine's CPU type, or a shell script. Shell scripts begin with a line containing the full path name of a shell (usually /bin/rc), prefixed by

bestie the original page genuinely just ends that sentence right there 😭 I'm not about to invent what comes next, fr.

The first word of a simple command cannot be a keyword unless it's quoted or otherwise disguised. The keywords are:

for in while if not switch fn ~ ! @

πŸŽ€ Arguments and Variables πŸŽ€

A number of constructions may be used where rc's syntax requires an argument to appear. In many cases a construction's value will be a list of arguments rather than a single string. slay.

The simplest kind of argument is the unquoted word: a sequence of one or more characters, none of which is a blank, tab, newline, or any of the following:

# ; & | ^ $ = ` ' { } ( ) < >

An unquoted word that contains any of the characters *, ?, [ is a pattern for matching against file names. 🌟

  • * matches any sequence of characters
  • ? matches any single character
  • [class] matches any character in the class

If the first character of class is ~, the class is complemented. The class may also contain pairs of characters separated by -, standing for all characters lexically between the two. The character / must appear explicitly in a pattern, as must the first character of the path name components . and ...

A pattern is replaced by a list of arguments, one for each path name matched β€” except that a pattern matching no names is not replaced by the empty list, but rather stands for itself. Pattern matching is done after all other operations. Thus,

x=/tmp echo $x^/*.c

matches /tmp/*.c, rather than matching /*.c and then prefixing /tmp. πŸ’€

A quoted word is a sequence of characters surrounded by single quotes ('). A single quote is represented in a quoted word by a pair of quotes ('').

Each of the following is an argument. 🫢

  • (arguments) β€” the value of a sequence of arguments enclosed in parentheses is a list comprising the members of each element of the sequence. Argument lists have no recursive structure, even though their syntax may suggest it. The following are entirely equivalent:
echo hi there everybody
((echo) (hi there) everybody)
  • $argument, $argument(subscript) β€” the argument after the $ is the name of a variable whose value is substituted. Multiple levels of indirection are possible, but of questionable utility, ngl. Variable values are lists of strings. If argument is a number n, the value is the nth element of $*, unless $* doesn't have n elements, in which case the value is empty. If argument is followed by a parenthesized list of subscripts, the value substituted is a list composed of the requested elements (origin 1). The parenthesis must follow the variable name with no spaces. Subscripts can also take the form m-n or m- to indicate a sequence of elements. Assignments to variables are described below.
  • $#argument β€” the value is the number of elements in the named variable. A variable that was never assigned a value has zero elements.
  • $"argument β€” the value is a single string containing the components of the named variable, separated by spaces. A variable with zero elements yields the empty string.
  • `{command} β€” rc executes the command and reads its standard output, splitting it into a list of arguments, using characters in $ifs as separators. If $ifs is not otherwise set, its value is ' \t\n'.
  • <{command}, >{command} β€” the command is executed asynchronously with its standard output or standard input connected to a pipe. The value of the argument is the name of a file referring to the other end of the pipe. This is what lets you build non-linear pipelines πŸ”₯ β€” for example, the following runs two commands old and new and uses cmp to compare their outputs:
cmp <{old} <{new}
  • argument^argument β€” the ^ operator concatenates its two operands. If the two operands have the same number of components, they're concatenated pairwise. If not, then one operand must have one component, and the other must be non-empty, and concatenation is distributive.

⭐ Free Carets ⭐

In most circumstances, rc will insert the ^ operator automatically between words that are not separated by white space.

  • Whenever one of $, ', ` follows a quoted or unquoted word, or an unquoted word follows a quoted word with no intervening blanks or tabs, a ^ is inserted between the two.
  • If an unquoted word immediately follows a $ and contains a character other than an alphanumeric, underscore, or *, a ^ is inserted before the first such character.

Thus

cc -$flags $stem.c

is equivalent to

cc -^$flags $stem^.c

πŸ’œ I/O Redirections πŸ’œ

The sequence >file redirects the standard output file (file descriptor 1, normally the terminal) to the named file; >>file appends standard output to the file.

The standard input file (file descriptor 0, also normally the terminal) may be redirected from a file by the sequence <file, or from an inline 'here document' by the sequence <<eof-marker.

The contents of a here document are lines of text taken from the command input stream up to a line containing nothing but the eof-marker, which may be either a quoted or unquoted word.

  • If eof-marker is unquoted, variable names of the form $word have their values substituted from rc's environment. If $word is followed by a caret (^), the caret is deleted.
  • If eof-marker is quoted, no substitution occurs.

Redirections may be applied to a file-descriptor other than standard input or output by qualifying the redirection operator with a number in square brackets. For example, the diagnostic output (file descriptor 2) may be redirected by writing cc junk.c >[2]junk.

A file descriptor may be redirected to an already open descriptor by writing >[fd0=fd1] or <[fd0=fd1]. fd1 is a previously opened file descriptor and fd0 becomes a new copy (in the sense of dup(3)) of it. A file descriptor may be closed by writing >[fd0=] or <[fd0=].

Redirections are executed from left to right. Therefore,

cc junk.c >/dev/null >[2=1]

and

cc junk.c >[2=1] >/dev/null

have different effects: the first puts standard output in /dev/null and then puts diagnostic output in the same place, whereas the second directs diagnostic output to the terminal and sends standard output to /dev/null. order matters bestie πŸ’…

πŸ‘‘ Compound Commands πŸ‘‘

A pair of commands separated by a pipe operator (|) is a command. The standard output of the left command is sent through a pipe to the standard input of the right command.

The pipe operator may be decorated to use different file descriptors:

  • |[fd] connects the output end of the pipe to file descriptor fd rather than 1.
  • |[fd0=fd1] connects output to fd1 of the left command and input to fd0 of the right command.

A pair of commands separated by && or || is a command. In either case, the left command is executed and its exit status examined.

  • If the operator is &&, the right command is executed if the left command's status is null.
  • || causes the right command to be executed if the left command's status is non-null.

The exit status of a command may be inverted (non-null is changed to null, null is changed to non-null) by preceding it with a !.

Precedence tea β˜•

  • | has the highest precedence, and is left-associative (i.e. it binds tighter to the left than the right).
  • ! has intermediate precedence.
  • && and || have the lowest precedence.

The unary @ operator, with precedence equal to !, causes its operand to be executed in a subshell.

Each of the following is a command. 🌟

  • if ( list ) command β€” a list is a sequence of commands, separated by &, ;, or newline. It is executed and if its exit status is null, the command is executed.
  • if not command β€” the immediately preceding command must have been if(list) command. If its condition was non-zero, the command is executed.

okay so next up in rc's little ✨language era✨: control flow. buckle up, bestie. πŸŽ€

  • for(name in arguments) command, for(name) command β€” the command gets executed once for each argument, with that argument assigned to name as it goes. If you leave the argument list out, $* is used. easy.
  • while(list) command β€” the list runs over and over until its exit status is non-null. every time it comes back with null status, the command runs. an empty list counts as null status, so heads up. πŸ¦‹
  • switch(argument){list} β€” rc hunts through the list for simple commands starting with the word case. (the search is only at the top level'</hotpink> of the list, so cases buried inside nested constructs are NOT found, they're just invisibly vibing, no cap.) the argument gets matched against each word following caseusing that pattern-matching algorithm from above, except that/and the first characters of.and..don't need to be matched explicitly. when a match lands, commands run up to the next followingcase` command (at the top level) or the closing brace. iconic.
  • {list} β€” braces exist to alter the grouping of commands implied by operator priorities. the body is a sequence of commands separated by &, ;, or newline.
  • fn name{list}, fn name β€” the first form defines a function with the given name. after that, whenever a command whose first argument is name shows up, the current value of the rest of that command's argument list gets assigned to $* (after saving its current value), and rc runs the list. the second form removes name's function definition. it's giving cleanup era. πŸ’…
  • fn note{list}, fn note β€” a function with a special name gets called when rc receives a corresponding note; see notify(3). the valid note names (and their notes) are sighup (hangup), sigint (interrupt), sigalrm (alarm), and sigfpe (floating point trap). by default rc exits on receiving any signal β€” except when it's run interactively, in which case interrupts and quits normally just make rc stop whatever it's doing and start reading a new command. the second form causes rc to handle a signal in the default manner again. Rc also knows an artificial note, sigexit, which fires when rc is about to finish executing. πŸ’€
  • name=argument command β€” any command may be preceded by a sequence of assignments interspersed with redirections. the assignments stay in effect until the end of the command, unless the command is empty (i.e. the assignments just stand alone), in which case they're effective until rescinded by later assignments.

πŸ› οΈ Built-in Commands

these ones run internally inside rc, usually because running them changes or depends on rc's internal state. no external binaries required, periodt.

  • . file ... β€” execute commands from file. $* is set for the duration to the rest of the argument list following file. file gets searched for using $path.
  • builtin command ... β€” execute command as usual, except any function named command is ignored in favor of the built-in meaning. "we're doing this the builtin way," no discussion. πŸ™ˆ
  • cd [dir] β€” change the current directory to dir. default argument is $home. dir is searched for in each of the directories mentioned in $cdpath.
  • eval [arg ...] β€” the arguments get concatenated, separated by spaces, into a single string, read as input to rc, and executed. glow-up via string, bestie. ✨
  • exec [command ...] β€” this instance of rc replaces itself with the given (non-built-in) command. ultimate main character move, there's no coming back.
  • flag f [+-] β€” set (+), clear (-), or test (neither + nor -) the flag f, where f is a single character, one of the command line flags (see Invocation, below).
  • exit [status] β€” exit with the given exit status. if none is given, the current value of $status is used.
  • 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)). with no flags 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. slay.
  • shift [n] β€” delete the first n (default 1) elements of $*.
  • wait [pid] β€” wait for the process with the given pid to exit. if no pid is given, all outstanding processes are waited for.
  • whatis name ... β€” print the value of each name in a form suitable for input to rc. the output is an assignment to any variable, the definition of any function, a call to builtin for any built-in command, or the completed pathname of any executable file.
  • ~ subject pattern ... β€” the subject is matched against each pattern in sequence. if it matches any pattern, $status is set to zero. otherwise, $status is set to one. patterns are the same as for file name matching, except that / and the first character of . and .. need not be matched explicitly. the patterns are not subjected to file name matching before the ~ command runs, so they don't need to be wrapped in quotation marks. πŸ”₯

🌍 Environment

the environment is a list of strings made available to executing binaries by the kernel. Rc creates an environment entry for each variable whose value is non-empty, and for each function. the string for a variable entry has the variable's name followed by = and its value. if the value has more than one component, those are separated by SOH (001) characters. the string for a function is just the rc input that defines the function. the name of a function in the environment is the function name preceded by (and then the source sentence just… stops. no cap, bestie, that's genuinely all it says β€” i'm not about to invent a character for you. πŸ™ˆ)

when rc starts executing it reads variable and function definitions from its environment.


🧚 Special Variables

these are the ones rc sets or uses. get familiar, bestie. πŸ’œ

  • $* β€” set to rc's argument list during initialization. whenever a . command or a function is executed, the current value is saved and $* receives the new argument list. the saved value is restored on completion of the . or function.
  • $apid β€” whenever a process is started asynchronously with &, $apid is set to its process id.
  • $home β€” the default directory for cd.
  • $ifs β€” the input field separators used in backquote substitutions. if $ifs is not set in rc's environment, it's initialized to blank, tab and newline.
  • $path β€” the search path used to find commands and input files for the . command. if not set in the environment, it's initialized by parsing the $PATH variable (as in sh(1)) or by path=(. /bin). the variables $path and $PATH are maintained together: changes to one will be reflected in the other.
  • $pid β€” set during initialization to rc's process id.
  • $prompt β€” when rc is run interactively, the first component of $prompt is printed before reading each command. the second component is printed whenever a newline is typed and more lines are required to complete the command. if not set in the environment, it's initialized by prompt=('% ' ' ').
  • $status β€” set to the wait message of the last-executed program. (unless it was started with &.) ! and ~ also change $status. its value is used to control execution in &&, ||, if and while commands. when rc exits at end-of-file of its input or on executing an exit command with no argument, $status is its exit status.

πŸš€ Invocation

if rc is started with no arguments it reads commands from standard input. otherwise its first non-flag argument is the name of a file from which to read commands (but see -c below). subsequent arguments become the initial value of $*. Rc accepts the following command-line flags. πŸ‘‘

  • -c string β€” commands are read from string.
  • -s β€” print out exit status after any command where the status is non-null.
  • -e β€” exit if $status is non-null after executing a simple command.
  • -i β€” if -i is present, or rc is given no arguments and its standard input is a terminal, it runs interactively. commands are prompted for using $prompt.
  • -I β€” makes sure rc is not run interactively.
  • -l β€” if -l is given or the first character of argument zero is -, rc reads commands from $home/lib/profile, if it exists, before reading its normal input.
  • -p β€” a no-op.
  • -d β€” a no-op.
  • -v β€” echo input on file descriptor 2 as it is read.
  • -x β€” print each simple command before executing it.
  • -r β€” print debugging information (internal form of commands as they are executed).

πŸ“‚ SOURCE

/src/cmd/rc

πŸ‘€ SEE ALSO

Tom Duff, ``Rc - The Plan 9 Shell''.


πŸ› BUGS

yeah, bestie, even icons have their flaws. 🫢

  • There should be a way to match patterns against whole lists rather than just single strings. (one day, fr fr.)
  • Using ~ to check the value of $status changes $status. messy of it, honestly.
  • Functions that use here documents don't work.
  • Free carets don't get inserted next to keywords.
  • The <{command} syntax depends on the underlying operating system providing a file descriptor device tree at /dev/fd.
  • By default, FreeBSD 5 does not provide file descriptors greater than 2 in /dev/fd. to fix this, add
/fdescfs    /dev/fd    fdescfs    rw    0    0

to /etc/fstab, and then mount /dev/fd. (adding the line to fstab makes FreeBSD mount the file system automatically at boot time.)


and that's the whole vibe check, bestie! πŸ’– this page is from RC(1plan9) β€” now go write some absolutely unhinged rc scripts and live your terminal main character era. πŸ‘‘βœ¨

πŸ“¦ where this page comes from