.TLThe Inferno Shell.AURoger Peppérog@vitanuova.com.ABThe Inferno shell.I shis a reasonably small shell that brings together aspects ofseveral other shells along with Inferno's dynamically loadedmodules, which it uses for much of the functionalitytraditionally built in to the shell. This paper focuses principallyon the features that make it unusual, and presentsan example ``network chat'' application written entirelyin.I shscript..AE.SHIntroduction.LPShells come in many shapes and sizes. The Infernoshell.I sh(actually one of three shells supplied with Inferno)is an attempt to combine the strengths of a Unix-likeshell, notably Tom Duff's.I rc ,with some of the features peculiar to Inferno.It owes its largest debt to.I rc ,which provides almost all of the syntaxand most of the semantics too; when in doubt,I copied.I rc 'sbehaviour.In fact, I borrowed as many good ideas as I couldfrom elsewhere, inventing new concepts and syntaxonly when unbearably tempted. See Creditsfor a list of those I could remember..LPThis paper does not attempt to give more thana brief overview of the aspects of.I shwhich it holds in common with Plan 9's.I rc .The reader is referredto.I sh (1)(the definitive reference)and Tom Duff's paper ``Rc - The Plan 9 Shell''.I have occasionally pinched examples from the latter,so the differences are easily contrasted..SHOverview.LP.I Shis, at its simplest level, a command interpreter that willbe familiar to all those who have used the Bourne-shell,C shell, or any of the numerous variants thereof (e.g..I bash ,.I ksh ,.I tcsh ).All of the following commands behave as expected:.P1datecat /lib/keyboardls -l > file.namesls -l /dis >> file.nameswc <fileecho [a-f]*.bls | wcls; datelimbo *.b &.P2An.I rcconcept that will be less familiar to usersof more conventional shells is the rôle of.I listsin the shell.Each simple.I shcommand, and the value of any.I shenvironment variable, consists of a list of words..I Shlists are flat, a simple ordered list of words,where a word is a sequence of characters thatmay include white-space or characters specialto the shell. The Bourne-shell and its kinhave no such concept, which means that everytime the value of any environment variable isused, it is split into blank separated words.For instance, the command:.P1x='-l /lib/keyboard'ls $x.P2would in many shells pass the two arguments.CW -l '' ``and.CW /lib/keyboard '' ``to the.CW lscommand.In.I sh ,it will pass the single argument.CW "-l /lib/keyboard" ''. ``.LPThe following aspects of.I sh 'ssyntax will be familiar to users of.I rc ..LPFile descriptor manipulation:.P1echo hello, world > /dev/null >[1=2].P2Environment variable values:.P1echo $var.P2Count number of elements in a variable:.P1echo $#var.P2Run a command and substitute its output:.P1rm `{grep -li microsoft *}.P2Lists:.P1echo (((a b) c) d).P2List concatenation:.P1cat /appl/cmd/sh/^(std regex expr)^.b.P2To the above,.I shadds a variant of the.CW `{}operator:\f5"{}\fP,which is the same except that it does notsplit the input into tokens,for example:.P1for i in "{echo one two three} { echo loop}.P2will only print.CW looponce..LP.I Shalso adds a new redirection operator.CW <> ,which opens the standard input (by default) forreading.I andwriting..SHCommand blocks.LPPossibly .I sh 'smost significant departure from thenorm is its use of command blocks as values.In a conventional shell, a command blockgroups commands together into a singlesyntactic unit that can then be used wherevera simple command might appear.For example:.P1{ echo hello echo goodbye} > /dev/null.P2.I Shallows this, but it also allows a command block to appearwherever a normal word would appear. In thiscase, the command block is not executed immediately,but is bundled up as if it was a single quoted word.For example:.P1cmd = { echo hello echo goodbye}.P2will store the contents of the braced block insidethe environment variable.CW $cmd .Printing the value of.CW $cmdgets the block back again, for example:.P1echo $cmd.P2gives.P1{echo hello;echo goodbye}.P2Note that when the shell parsed the block,it ignored everything that was notsyntactically relevant to the executionof the block; for instance, the white spacehas been reduced to the minimum necessary,and the newline has been changed tothe functionally identical semi-colon..LPIt is also worth pointing out that.CW echois an external module, implementing only thestandard.I Command (2)interface; it has no knowledge of shell commandblocks. When the shell invokes an external command,and one of the arguments is a command block,it simply passes the equivalent string. Internally, built in commandsare slightly different for efficiency's sake, as we will see,but for almost all purposes you can treat command blocksas if they were strings holding functionally equivalent shell commands..LPThis equivalence also applies to the execution of commands.When theshell comes to execute a simple command (a sequence ofwords), it examines the first word to decide what to execute.In most shells, this word can be either the file name ofan external command, or the name of a command built into the shell (e.g..CW exit )..LP.I Shfollows these conventional rules, but first, it examinesthe first character of the first word, and if it is an openbrace.CW { ) (character, it treats it as a command block,parses it, and executes it according to the normal syntaxrules of the shell. For the duration of this execution, itsets the environment variable.CW $*to the list of arguments passed to the block. For example:.P1{echo $*} hello world.P2is exactly the same as.P1echo hello world.P2Execution of command blocks is the same whetherthe command block is just a string or has already beenparsed by the shell.For example:.P1{echo hello}.P2is exactly the same as.P1\&'{echo hello}'.P2The only difference is that the former case has its syntaxchecked for correctness as soon as the shell sees the script;whereas if the latter contained a malformed command block,a syntax error will be raised only when itcomes to actually execute the command..LPThe shell's treatment of braces can be used to provide functionalitysimilar to the.CW evalcommand that is built in to most other shells..P1cmd = 'echo hello; echo goodbye'\&'{'^$cmd^'}'.P2In other words, simply by surrounding a stringby braces and executing it, the stringwill be executed as if it had been typed to theshell. Note the use of the caret.CW ^ ) (string concatenatation operator..I Shdoes provide `free carets' in the same way as.I rc ,so in the previous example.P1\&'{'$cmd'}'.P2would work exactly the same, but generally,and in particular when writing scripts, it isgood style to make the carets explicit..SHAssignment and scope.LPThe assignment operator in.I sh ,in common with most other shellsis.CW = ..P1x=a b c d.P2assigns the four element list.CW "(a b c d)"to the environment variable named.CW x .The value can later be extractedwith the.CW $operator, for example:.P1echo $x.P2will print.P1a b c d.P2.I Shalso implements a form of local variable.An execution of a braced block commandcreates a new scope for the duration of that block;the value of a variable assigned with.CW :=in that block will be lost when theblock exits. For example:.P1x = hello{x := goodbye }echo $x.P2will print ``hello''.Note that the scoping rules are.I dynamic\- variable references are interpretedrelative to their containing scope at execution time.For example:.P1x := hellocmd := {echo $x}{ x := goodbye $cmd}.P2wil print ``goodbye'', not ``hello''. For oneway of avoiding this problem, see ``Lexicalbinding'' below..LPOne late, but useful, addition to the shell's assignmentsyntax is tuple assignment. This partiallymakes up for the lack of list indexing primitives in the shell.If the left hand side of the assignment operator isa list of variable names, each element of the list on theright hand side is assigned in turn to its respective variable.The last variable mentioned gets assigned all theremaining elements.For example, after:.P1(a b c) := (one two three four five).P2.CW ais.CW one ,.CW bis.CW two ,and.CW ccontains the three element list.CW "(three four five)".For example:.P1(first var) = $var.P2knocks the first element off.CW $varand puts it in.CW $first ..LPOne important difference between.I sh 'svariables and variables in shells underUnix-like operating systems derives fromthe fact that Inferno's underlying processcreation primitive is.I spawn ,not.I fork .This means that, even though the shellmight create a new process to accomplishan I/O redirection, variables changed bythe sub-process are still visible in the parentprocess. This applies anywhere a new processis created that runs synchronously with respectto the rest of the shell script - i.e. there is nochance of parallel access to the environment.For example, it is possible to getaccess to the status value of a command executedby the.CW `{}operator:.P1files=`{du -a; dustatus = $status}if {! ~ $dustatus ''} { echo du failed}.P2When the shell does spawn an asynchronousprocess (background processes and pipelinesare the two occasions that it does so), theenvironment is copied so changes in oneprocess do not affect another..SHLoadable modules.LPThe ability to pass command blocks as values isall very well, but does not in itself provide theprogrammability that is central to the power of shell scriptsand is built in to most shells, the conditionalexecution of commands, for instance.The Inferno shell is different;it provides no programmability within the shell itself,but instead relies on external modules to provide this.It has a built in command.CW loadthat loads a new module into the shell. The modulethat supports standard control flow functionalityand a number of other useful tidbits is called.CW std ..P1load std.P2loads this module into the shell..CW Stdis a Dis module thatimplements the.CW Shellbuiltininterface; the shell looks in the directory.CW /dis/shfor the module file, in this case.CW /dis/sh/std.dis ..LPWhen a module is loaded, it is given the opportunityto define as many new commands as it wants.Perhaps slightly confusingly, these are known as``built-in'' commands (or just ``builtins''), to distinguishthem from commands executed in a separate processwith no access to shell internals. Built-incommands run in the same process as the shell, andhave direct access to all its internal state (environment variables,command line options, and state stored within the implementingmodule itself). It is possible to find outwhat built-in commands are currently defined withthe command.CW loaded .Before any modules have been loaded, typing.P1loaded.P2produces:.P1builtin builtinexit builtinload builtinloaded builtinrun builtinunload builtinwhatis builtin${builtin} builtin${loaded} builtin${quote} builtin${unquote} builtin.P2These are all the commands that are built in to theshell proper; I'll explain the.CW ${}commands later.After loading.CW std ,executing.CW loadedproduces:.P1! stdand stdapply stdbuiltin builtinexit builtinflag stdfn stdfor stdgetlines stdif stdload builtinLines 1–500 of 2067. Load more
.TL The Inferno Shell .AU Roger Peppé rog@vitanuova.com .AB The Inferno shell .I sh is a reasonably small shell that brings together aspects of several other shells along with Inferno's dynamically loaded modules, which it uses for much of the functionality traditionally built in to the shell. This paper focuses principally on the features that make it unusual, and presents an example ``network chat'' application written entirely in .I sh script. .AE .SH Introduction .LP Shells come in many shapes and sizes. The Inferno shell .I sh (actually one of three shells supplied with Inferno) is an attempt to combine the strengths of a Unix-like shell, notably Tom Duff's .I rc , with some of the features peculiar to Inferno. It owes its largest debt to .I rc , which provides almost all of the syntax and most of the semantics too; when in doubt, I copied .I rc 's behaviour. In fact, I borrowed as many good ideas as I could from elsewhere, inventing new concepts and syntax only when unbearably tempted. See Credits for a list of those I could remember. .LP This paper does not attempt to give more than a brief overview of the aspects of .I sh which it holds in common with Plan 9's .I rc . The reader is referred to .I sh (1) (the definitive reference) and Tom Duff's paper ``Rc - The Plan 9 Shell''. I have occasionally pinched examples from the latter, so the differences are easily contrasted. .SH Overview .LP .I Sh is, at its simplest level, a command interpreter that will be familiar to all those who have used the Bourne-shell, C shell, or any of the numerous variants thereof (e.g. .I bash , .I ksh , .I tcsh ). All of the following commands behave as expected: .P1 date cat /lib/keyboard ls -l > file.names ls -l /dis >> file.names wc <file echo [a-f]*.b ls | wc ls; date limbo *.b & .P2 An .I rc concept that will be less familiar to users of more conventional shells is the rôle of .I lists in the shell. Each simple .I sh command, and the value of any .I sh environment variable, consists of a list of words. .I Sh lists are flat, a simple ordered list of words, where a word is a sequence of characters that may include white-space or characters special to the shell. The Bourne-shell and its kin have no such concept, which means that every time the value of any environment variable is used, it is split into blank separated words. For instance, the command: .P1 x='-l /lib/keyboard' ls $x .P2 would in many shells pass the two arguments .CW -l '' `` and .CW /lib/keyboard '' `` to the .CW ls command. In .I sh , it will pass the single argument .CW "-l /lib/keyboard" ''. `` .LP The following aspects of .I sh 's syntax will be familiar to users of .I rc . .LP File descriptor manipulation: .P1 echo hello, world > /dev/null >[1=2] .P2 Environment variable values: .P1 echo $var .P2 Count number of elements in a variable: .P1 echo $#var .P2 Run a command and substitute its output: .P1 rm `{grep -li microsoft *} .P2 Lists: .P1 echo (((a b) c) d) .P2 List concatenation: .P1 cat /appl/cmd/sh/^(std regex expr)^.b .P2 To the above, .I sh adds a variant of the .CW `{} operator: \f5"{}\fP, which is the same except that it does not split the input into tokens, for example: .P1 for i in "{echo one two three} { echo loop } .P2 will only print .CW loop once. .LP .I Sh also adds a new redirection operator .CW <> , which opens the standard input (by default) for reading .I and writing. .SH Command blocks .LP Possibly .I sh 's most significant departure from the norm is its use of command blocks as values. In a conventional shell, a command block groups commands together into a single syntactic unit that can then be used wherever a simple command might appear. For example: .P1 { echo hello echo goodbye } > /dev/null .P2 .I Sh allows this, but it also allows a command block to appear wherever a normal word would appear. In this case, the command block is not executed immediately, but is bundled up as if it was a single quoted word. For example: .P1 cmd = { echo hello echo goodbye } .P2 will store the contents of the braced block inside the environment variable .CW $cmd . Printing the value of .CW $cmd gets the block back again, for example: .P1 echo $cmd .P2 gives .P1 {echo hello;echo goodbye} .P2 Note that when the shell parsed the block, it ignored everything that was not syntactically relevant to the execution of the block; for instance, the white space has been reduced to the minimum necessary, and the newline has been changed to the functionally identical semi-colon. .LP It is also worth pointing out that .CW echo is an external module, implementing only the standard .I Command (2) interface; it has no knowledge of shell command blocks. When the shell invokes an external command, and one of the arguments is a command block, it simply passes the equivalent string. Internally, built in commands are slightly different for efficiency's sake, as we will see, but for almost all purposes you can treat command blocks as if they were strings holding functionally equivalent shell commands. .LP This equivalence also applies to the execution of commands. When the shell comes to execute a simple command (a sequence of words), it examines the first word to decide what to execute. In most shells, this word can be either the file name of an external command, or the name of a command built in to the shell (e.g. .CW exit ). .LP .I Sh follows these conventional rules, but first, it examines the first character of the first word, and if it is an open brace .CW { ) ( character, it treats it as a command block, parses it, and executes it according to the normal syntax rules of the shell. For the duration of this execution, it sets the environment variable .CW $* to the list of arguments passed to the block. For example: .P1 {echo $*} hello world .P2 is exactly the same as .P1 echo hello world .P2 Execution of command blocks is the same whether the command block is just a string or has already been parsed by the shell. For example: .P1 {echo hello} .P2 is exactly the same as .P1 \&'{echo hello}' .P2 The only difference is that the former case has its syntax checked for correctness as soon as the shell sees the script; whereas if the latter contained a malformed command block, a syntax error will be raised only when it comes to actually execute the command. .LP The shell's treatment of braces can be used to provide functionality similar to the .CW eval command that is built in to most other shells. .P1 cmd = 'echo hello; echo goodbye' \&'{'^$cmd^'}' .P2 In other words, simply by surrounding a string by braces and executing it, the string will be executed as if it had been typed to the shell. Note the use of the caret .CW ^ ) ( string concatenatation operator. .I Sh does provide `free carets' in the same way as .I rc , so in the previous example .P1 \&'{'$cmd'}' .P2 would work exactly the same, but generally, and in particular when writing scripts, it is good style to make the carets explicit. .SH Assignment and scope .LP The assignment operator in .I sh , in common with most other shells is .CW = . .P1 x=a b c d .P2 assigns the four element list .CW "(a b c d)" to the environment variable named .CW x . The value can later be extracted with the .CW $ operator, for example: .P1 echo $x .P2 will print .P1 a b c d .P2 .I Sh also implements a form of local variable. An execution of a braced block command creates a new scope for the duration of that block; the value of a variable assigned with .CW := in that block will be lost when the block exits. For example: .P1 x = hello {x := goodbye } echo $x .P2 will print ``hello''. Note that the scoping rules are .I dynamic \- variable references are interpreted relative to their containing scope at execution time. For example: .P1 x := hello cmd := {echo $x} { x := goodbye $cmd } .P2 wil print ``goodbye'', not ``hello''. For one way of avoiding this problem, see ``Lexical binding'' below. .LP One late, but useful, addition to the shell's assignment syntax is tuple assignment. This partially makes up for the lack of list indexing primitives in the shell. If the left hand side of the assignment operator is a list of variable names, each element of the list on the right hand side is assigned in turn to its respective variable. The last variable mentioned gets assigned all the remaining elements. For example, after: .P1 (a b c) := (one two three four five) .P2 .CW a is .CW one , .CW b is .CW two , and .CW c contains the three element list .CW "(three four five)". For example: .P1 (first var) = $var .P2 knocks the first element off .CW $var and puts it in .CW $first . .LP One important difference between .I sh 's variables and variables in shells under Unix-like operating systems derives from the fact that Inferno's underlying process creation primitive is .I spawn , not .I fork . This means that, even though the shell might create a new process to accomplish an I/O redirection, variables changed by the sub-process are still visible in the parent process. This applies anywhere a new process is created that runs synchronously with respect to the rest of the shell script - i.e. there is no chance of parallel access to the environment. For example, it is possible to get access to the status value of a command executed by the .CW `{} operator: .P1 files=`{du -a; dustatus = $status} if {! ~ $dustatus ''} { echo du failed } .P2 When the shell does spawn an asynchronous process (background processes and pipelines are the two occasions that it does so), the environment is copied so changes in one process do not affect another. .SH Loadable modules .LP The ability to pass command blocks as values is all very well, but does not in itself provide the programmability that is central to the power of shell scripts and is built in to most shells, the conditional execution of commands, for instance. The Inferno shell is different; it provides no programmability within the shell itself, but instead relies on external modules to provide this. It has a built in command .CW load that loads a new module into the shell. The module that supports standard control flow functionality and a number of other useful tidbits is called .CW std . .P1 load std .P2 loads this module into the shell. .CW Std is a Dis module that implements the .CW Shellbuiltin interface; the shell looks in the directory .CW /dis/sh for the module file, in this case .CW /dis/sh/std.dis . .LP When a module is loaded, it is given the opportunity to define as many new commands as it wants. Perhaps slightly confusingly, these are known as ``built-in'' commands (or just ``builtins''), to distinguish them from commands executed in a separate process with no access to shell internals. Built-in commands run in the same process as the shell, and have direct access to all its internal state (environment variables, command line options, and state stored within the implementing module itself). It is possible to find out what built-in commands are currently defined with the command .CW loaded . Before any modules have been loaded, typing .P1 loaded .P2 produces: .P1 builtin builtin exit builtin load builtin loaded builtin run builtin unload builtin whatis builtin ${builtin} builtin ${loaded} builtin ${quote} builtin ${unquote} builtin .P2 These are all the commands that are built in to the shell proper; I'll explain the .CW ${} commands later. After loading .CW std , executing .CW loaded produces: .P1 ! std and std apply std builtin builtin exit builtin flag std fn std for std getlines std if std load builtin