123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067 |
- .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
- loaded builtin
- .P3
- or std
- pctl std
- raise std
- rescue std
- run builtin
- status std
- subfn std
- unload builtin
- whatis builtin
- while std
- ~ std
- .P3
- ${builtin} builtin
- ${env} std
- ${hd} std
- ${index} std
- ${join} std
- ${loaded} builtin
- ${parse} std
- ${pid} std
- ${pipe} std
- ${quote} builtin
- ${split} std
- ${tl} std
- ${unquote} builtin
- .P2
- The name of each command defined
- by a loaded module is followed by the name of
- the module, so you can see that in this case
- .CW std
- has defined commands such as
- .CW if
- and
- .CW while .
- These commands are reminiscent of the
- commands built in to the syntax of
- other shells, but have no special syntax
- associated with them: they obey the normal
- argument gathering and execution semantics.
- .LP
- As an example, consider the
- .CW for
- command.
- .P1
- for i in a b c d {
- echo $i
- }
- .P2
- This command traverses the list
- .CW "(a b c d)"
- executing
- .CW "{echo $i}"
- with
- .CW $i
- set to each element in turn. In
- .I rc ,
- this might be written
- .P1
- for (i in a b c d) {
- echo $i
- }
- .P2
- and in fact, in
- .I sh ,
- this is exactly equivalent. The round brackets
- denote a list and, like
- .I rc ,
- all lists are flattened before passing to an
- executed command.
- Unlike the
- .CW for
- command in
- .I rc ,
- the braces around the command are
- not optional; as with the arguments to
- a normal command, gathering of arguments
- stops at a newline. The exception to this rule
- is that newlines within brackets are treated as white space.
- This last rule also
- applies to round brackets, for example:
- .P1
- (for i in
- a
- b
- c
- d
- {echo $i}
- )
- .P2
- does the same thing.
- This is very useful for commands that take multiple
- command block arguments, and is actually the only
- line continuation mechanism that
- .I sh
- provides (the usual backslash
- .CW \e ) (
- character is not in any way special to
- .I sh ).
- .SH
- Control structures
- .LP
- Inferno commands, like shell commands in Unix
- or Plan 9, return a status when they finish.
- A command's status in Inferno is a short string
- describing any error that has occurred;
- it can be found in the environment variable
- .CW $status .
- This is the value that commands defined by
- .CW std
- use to determine conditional
- execution - if it is empty, it is true; otherwise
- false.
- .CW Std
- defines, for instance, a command
- .CW ~
- that provides a simple pattern matching capability.
- Its first argument is the string to test the patterns
- against, and subsequent arguments give the patterns,
- in normal shell wildcard syntax; its status is true
- if there is a match.
- .P1
- ~ sh.y '*.y'
- ~ std.b '*.y'
- .P2
- give true and false statuses respectively.
- A couple of pitfalls lurk here for the unwary:
- unlike its
- .I rc
- namesake, the patterns
- .I are
- expanded by the shell if left unquoted, so
- one has to be careful to quote wildcard characters,
- or escape them with a backslash if they are to
- be used literally.
- Like any other command,
- .CW ~
- receives a simple list of arguments, so it has to
- assume that the string tested has exactly one element;
- if you provide a null variable, or one with more
- than one element, then you will get unexpected results.
- If in doubt, use the
- \f5$"\fP
- operator to make sure of that.
- .LP
- Used in conjunction with the
- .CW $#
- operator,
- .CW ~
- provides a way to check the
- number of elements in a list:
- .P1
- ~ $#var 0
- .P2
- will be true if
- .CW $var
- is empty.
- .LP
- This can be tested by the
- .CW if
- command, which
- accepts command blocks for
- its arguments, executing its second argument if
- the status of the first is empty (true).
- For example:
- .P1
- if {~ $#var 0} {
- echo '$var has no elements'
- }
- .P2
- Note that the start of one argument must
- come on the same line as the end of of the previous,
- otherwise it will be treated as a new command,
- and always executed. For example:
- .P1
- if {~ $#var 0}
- {echo '$var has no elements'} # this will always be executed
- .P2
- The way to get around this is to use list bracketing,
- for example:
- .P1
- (if {~ $#var 0}
- {echo '$var has no elements'}
- )
- .P2
- will have the desired effect.
- The
- .CW if
- command is more general than
- .I rc 's
- .CW if ,
- in that it accepts an arbitrary number
- of condition/action pairs, and executes each condition
- in turn until one is true, whereupon it executes the associated
- action. If the last condition has no action, then it
- acts as the ``else'' clause in the
- .CW if .
- For example:
- .P1
- (if {~ $#var 0} {
- echo zero elements
- }
- {~ $#var 1} {
- echo one element
- }
- {echo more than one element}
- )
- .P2
- .LP
- .CW Std
- provides various other control structures.
- .CW And
- and
- .CW or
- provide the equivalent of
- .I rc 's
- .CW &&
- and
- .CW ||
- operators. They each take any number of command
- block arguments and conditionally execute each
- in turn.
- .CW And
- stops executing when a block's status is false,
- .CW or
- when a block's status is true:
- .P1
- and {~ $#var 1} {~ $var '*.sbl'} {echo variable ends in .sbl}
- (or {mount /dev/eia0 /n/remote}
- {echo mount has failed with $status}
- )
- .P2
- An extremely easy trap to fall into is to use
- .CW $*
- inside a block assuming that its value is the
- same as that outside the block. For instance:
- .P1
- # this will not work
- if {~ $#* 2} {echo two arguments}
- .P2
- It will not work because
- .CW $*
- is set locally for every block, whether it
- is given arguments or not. A solution is to
- assign
- .CW $*
- to a variable at the start of the block:
- .P1
- args = $*
- if {~ $#args 2} {echo two arguments}
- .P2
- .LP
- .CW While
- provides looping, executing its second argument
- as long as the status of the first remains true.
- As the status of an empty block is always true,
- .P1
- while {} {echo yes}
- .P2
- will loop forever printing ``yes''.
- Another looping command is
- .CW getlines ,
- which loops reading lines from its standard
- input, and executing its command argument,
- setting the environment variable
- .CW $line
- to each line in turn.
- For example:
- .P1
- getlines {
- echo '#' $line
- } < x.b
- .P2
- will print each line of the file
- .CW x.b
- preceded by a
- .CW #
- character.
- .SH
- Exceptions
- .LP
- When the shell encounters some error conditions, such
- as a parsing error, or a redirection failure,
- it prints a message to standard error and raises
- an
- .I exception .
- In an interactive shell this is caught by the interactive
- command loop; in a script it will cause an exit with
- a false status, unless handled.
- .LP
- Exceptions can be handled and raised with the
- .CW rescue
- and
- .CW raise
- commands provided by
- .CW std .
- An exception has a short string associated with it.
- .P1
- raise error
- .P2
- will raise an exception named ``error''.
- .P1
- rescue error {echo an error has occurred} {
- command
- }
- .P2
- will execute
- .CW command
- and will, in the event that it raises an
- .CW error
- exception, print a diagnostic message.
- The name of the exception given to
- .CW rescue
- can end in an asterisk
- .CW * ), (
- which will match any exception starting with
- the preceding characters. The
- .CW *
- needs quoting to avoid being expanded as a wildcard
- by the shell.
- .P1
- rescue '*' {echo caught an exception $exception} {
- command
- }
- .P2
- will catch all exceptions raised by
- .CW command ,
- regardless of name.
- Within the handler block,
- .CW rescue
- sets the environment variable
- .CW $exception
- to the actual name of the exception caught.
- .LP
- Exceptions can be caught only within a single
- process \- if an exception is not caught, then
- the name of the exception becomes the
- exit status of the process.
- As
- .I sh
- starts a new process for commands with redirected
- I/O, this means that
- .P1
- raise error
- echo got here
- .P2
- behaves differently to:
- .P1
- raise error > /dev/null
- echo got here
- .P2
- The former prints nothing, while the latter
- prints ``got here''.
- .LP
- The exceptions
- .CW break
- and
- .CW continue
- are recognised by
- .CW std 's
- looping commands
- .CW for ,
- .CW while ,
- and
- .CW getlines .
- A
- .CW break
- exception causes the loop to terminate;
- a
- .CW continue
- exception causes the loop to continue
- as before. For example:
- .P1
- for i in * {
- if {~ $i 'r*'} {
- echo found $i
- raise break
- }
- }
- .P2
- will print the name of the first
- file beginning with ``r'' in the
- current directory.
- .SH
- Substitution builtins
- .LP
- In addition to normal commands, a loaded module
- can also define
- .I "substitution builtin"
- commands. These are different from normal commands
- in that they are executed as part of the argument
- gathering process of a command, and instead of
- returning an exit status, they yield a list of values
- to be used as arguments to a command. They
- can be thought of as a kind of `active environment variable',
- whose value is created every time it is referenced.
- For example, the
- .CW split
- substitution builtin defined by
- .CW std
- splits up a single argument into strings separated
- by characters in its first argument:
- .P1
- echo ${split e 'hello there'}
- .P2
- will print
- .P1
- h llo th r
- .P2
- Note that, unlike the conventional shell
- backquote operator, the result of the
- .CW $
- command is not re-interpreted, for example:
- .P1
- for i in ${split e 'hello there'} {
- echo arg $i
- }
- .P2
- will print
- .P1
- arg h
- arg llo th
- arg r
- .P2
- Substitution builtins can only be named
- as the initial command inside a dollar-referenced
- command block - they live in a different namespace
- from that of normal commands.
- For instance,
- .CW loaded
- and
- .CW ${loaded}
- are quite distinct: the former prints a list
- of all builtin names and their defining modules, whereas
- the former yields a list of all the currently loaded
- modules.
- .LP
- .CW Std
- provides a number of useful commands
- in the form of substitution builtins.
- .CW ${join}
- is the complement of
- .CW ${split} :
- it joins together any elements in its argument list
- using its first argument as the separator, for example:
- .P1
- echo ${join . file tar gz}
- .P2
- will print:
- .P1
- file.tar.gz
- .P2
- The in-built shell operator
- \f5$"\fP
- is exactly equivalent to
- .CW ${join}
- with a space as its first argument.
- .LP
- List indexing is provided with
- .CW ${index} ,
- which given a numeric index and a list
- yields the
- .I index 'th
- item in the list (origin 1). For example:
- .P1
- echo ${index 4 one two three four five}
- .P2
- will print
- .P1
- four
- .P2
- A pair of substitution builtins with some of
- the most interesting uses are defined by
- the shell itself:
- .CW ${quote}
- packages its argument list into a single
- string in such a way that it can be later
- parsed by the shell and turned back into the same list.
- This entails quoting any items in the list
- that contain shell metacharacters, such as
- .CW ; ` '
- or
- .CW & '. `
- For example:
- .P1
- x='a;' 'b' 'c d' ''
- echo $x
- echo ${quote $x}
- .P2
- will print
- .P1
- a; b c d
- \&'a;' b 'c d' ''
- .P2
- Travel in the reverse direction is possible
- using
- .CW ${unquote} ,
- which takes a single string, as produced by
- .CW ${quote} ,
- and produces the original list again.
- There are situations in
- .I sh
- where only a single string can be used, but
- it is useful to be able to pass around the values
- of arbitrary
- .I sh
- variables in this form;
- .CW ${quote}
- and
- .CW ${unquote}
- between them make this possible. For instance
- the value of a
- .I sh
- list can be stored in a file and later retrieved
- without loss. They are also useful to implement
- various types of behaviour involving automatically
- constructed shell scripts; see ``Lexical binding'', below,
- for an example.
- .LP
- Two more list manipulation commands provided
- by
- .CW std
- are
- .CW ${hd}
- and
- .CW ${tl} ,
- which mirror their Limbo namesakes:
- .CW ${hd}
- returns the first element of a list,
- .CW ${tl}
- returns all but the first element of a list.
- For example:
- .P1
- x=one two three four
- echo ${hd $x}
- echo ${tl $x}
- .P2
- will print:
- .P1
- one
- two three four
- .P2
- Unlike their Limbo counterparts, they
- do not complain if their argument list
- is not long enough; they just yield a null list.
- .LP
- .CW Std
- provides three other substitution builtins of
- note.
- .CW ${pid}
- yields the process id of the current
- process.
- .CW ${pipe}
- provides a somewhat more cumbersome equivalent of the
- .CW >{}
- and
- .CW <{}
- commands found in
- .I rc ,
- i.e. branching pipelines.
- For example:
- .P1
- cmp ${pipe from {old}} ${pipe from {new}}
- .P2
- will regression-test a new version of a command.
- Using
- .CW ${pipe}
- yields the name of a file in the namespace
- which is a pipe to its argument command.
- .LP
- The substitution builtin
- .CW ${parse}
- is used to check shell syntax without actually
- executing a command. The command:
- .P1
- x=${parse '{echo hello, world}'}
- .P2
- will return a parsed version of the string
- .CW "echo hello, world" ''; ``
- if an error occurs, then a
- .CW "parse error"
- exception will be raised.
- .SH
- Functions
- .LP
- Shell functions are a facility provided
- by the
- .CW std
- shell module; they associate a command
- name with some code to execute when
- that command is named.
- .P1
- fn hello {
- echo hello, world
- }
- .P2
- defines a new command,
- .CW hello ,
- that prints a message when executed.
- The command is passed arguments in the
- usual way, for example:
- .P1
- fn removems {
- for i in $* {
- if {grep -s Microsoft $i} {
- rm $i
- }
- }
- }
- removems *
- .P2
- will remove all files in the current directory
- that contain the string ``Microsoft''.
- .LP
- The
- .CW status
- command provides a way to return an
- arbitrary status from a function. It takes
- a single argument \- its exit status
- is the value of that argument. For instance:
- .P1
- fn false {
- status false
- }
- fn true {
- status ''
- }
- .P2
- It is also possible to define new substitution builtins
- with the command
- .CW subfn :
- the value of
- .CW $result
- at the end of the execution of the
- command gives the value yielded.
- For example:
- .P1
- subfn backwards {
- for i in $* {
- result=$i $result
- }
- }
- echo ${backwards a b c 'd e'}
- .P2
- will reverse a list, producing:
- .P1
- d e c b a
- .P2
- .LP
- The commands associated with shell functions
- are stored as normal environment variables, and
- so are exported to external commands in the usual
- way.
- .CW Fn
- definitions are stored in environment variables
- starting
- .CW fn- ;
- .CW subfn
- definitions use environment variables starting
- .CW sfn- .
- It is useful to know this, as the shell core knows
- nothing of these functions - they look just like
- builtin commands defined by
- .CW std ;
- looking at the current definition of
- .CW $fn-\fIname\fP
- is the only way of finding out the body of code
- associated with function
- .I name .
- .SH
- Other loadable
- .I sh
- modules
- .LP
- In addition to
- .CW std ,
- and
- .CW tk ,
- which is mentioned later, there are
- several loadable
- .I sh
- modules that extend
- .I sh's
- functionality.
- .LP
- .CW Expr
- provides a very simple stack-based calculator,
- giving simple arithmetic capability to the shell.
- For example:
- .P1
- load expr
- echo ${expr 3 2 1 + x}
- .P2
- will print
- .CW 9 .
- .LP
- .CW String
- provides shell level access to the Limbo
- string library routines. For example:
- .P1
- load string
- echo ${tolower 'Hello, WORLD'}
- .P2
- will print
- .P1
- hello, world
- .P2
- .CW Regex
- provides regular expression matching and
- substitution operations. For instance:
- .P1
- load regex
- if {! match '^[a-z0-9_]+$' $line} {
- echo line contains invalid characters
- }
- .P2
- .CW File2chan
- provides a way for a shell script to create a
- file in the namespace with properties
- under its control. For instance:
- .P1
- load file2chan
- (file2chan /chan/myfile
- {echo read request from /chan/myfile}
- {echo write request to /chan/myfile}
- )
- .P2
- .CW Arg
- provides support for the parsing of standard
- Unix-style options.
- .SH
- .I Sh
- and Inferno devices
- .LP
- Devices under Inferno are implemented as files,
- and usually device interaction consists of simple
- strings written or read from the device files.
- This is a happy coincidence, as the two things
- that
- .I sh
- does best are file manipulation and string manipulation.
- This means that
- .I sh
- scripts can exploit the power of direct access to
- devices without the need to write more long winded
- Limbo programs. You do not get the type checking
- that Limbo gives you, and it is not quick, but for
- knocking up quick prototypes, or ``wrapper scripts'',
- it can be very useful.
- .LP
- Consider the way that Inferno implements network
- access, for example. A file called
- .CW /net/cs
- implements DNS address translation. A string such as
- .CW tcp!www.vitanuova.com!telnet
- is written to
- .CW /net/cs ;
- the translated form of the address is then read
- back, in the form of a (\fIfile\fP, \fItext\fP)
- pair, where
- .I file
- is the name of a
- .I clone
- file in the
- .CW /net
- directory
- (e.g.
- .CW /net/tcp/clone ),
- and
- .I text
- is a translated address as understood by the relevant
- network (e.g.
- .CW 194.217.172.25!23 ).
- We can write a shell function that performs this
- translation, returning a triple
- (\fIdirectory\fP \fIclonefile\fP \fItext\fP):
- .P1
- subfn cs {
- addr := $1
- or {
- <> /net/cs {
- (if {echo -n $addr >[1=0]} {
- (clone addr) := `{read 8192 0}
- netdir := ${dirname $clone}
- result=$netdir $clone $addr
- } {
- echo 'cs: cannot translate "' ^
- $addr ^
- '":' $status >[1=2]
- status failed
- }
- )
- }
- } {raise 'cs failed'}
- }
- .P2
- The code
- .P1
- <> /net/cs { \fR....\fP }
- .P2
- opens
- .CW /net/cs
- for reading and writing, on the standard input;
- the code inside the braces can then read and
- write it.
- If the address translation fails, an error will
- be generated on the write, so the
- .CW echo
- will fail - this is detected, and an appropriate exit status
- set.
- Being a substitution function, the only way that
- .CW cs
- can indicate an error is by raising an exception, but
- exceptions do not propagate across processes
- (a new process is created as a result of the redirection),
- hence the need for the status check and the raised exception
- on failure.
- .LP
- The external program
- .CW read
- is invoked to make a single read of the
- result from
- .CW /lib/cs .
- It takes a block size, and a read offset - it
- is important to set this, as the initial write of the
- address to
- .CW /lib/cs
- will have advanced the file offset, and we will miss
- a chunk of the returned address if we're not careful.
- .LP
- .CW Dirname
- is a little shell function that uses one of the
- .I string
- builtin functions to get the directory name from
- the pathname of the
- .I clone
- file. It looks like:
- .P1
- load string
- subfn dirname {
- result = ${hd ${splitr $1 /}}
- }
- .P2
- Now we have an address translation function, we can
- access the network interface directly. There are
- three main operations possible with Inferno network
- devices: connecting to a remote address, announcing
- the availability of a local dial-in address, and listening
- for an incoming connection on a previously announced
- address. They are accessed in similar ways (see
- .I ip (3)
- for details):
- .LP
- The dial and announce operations require a new
- .CW net
- directory, which is created by reading the
- clone file - this actually opens the
- .CW ctl
- file in a newly created net directory, representing
- one end of a network connection. Reading a
- .CW ctl
- file yields the name of the new directory;
- this enables an application to find the associated
- .CW data
- file; reads and writes to this file go to the
- other end of the network connection.
- The listen operation is similar, but the new
- net directory is created by reading from an existing
- directory's
- .CW listen
- file.
- .LP
- Here is a
- .I sh
- function that implements some behaviour common
- to all three operations:
- .P1
- fn newnetcon {
- (netdir constr datacmd) := $*
- id := "{read 20 0}
- or {~ $constr ''} {echo -n $constr >[1=0]} {
- echo cannot $constr >[1=2]
- raise failed
- }
- net := $netdir/^$id
- $datacmd <> $net^/data
- }
- .P2
- It takes the name of a network protocol directory
- (e.g.
- .CW /net/tcp ),
- a possibly empty string to write into the control
- file when the new directory id has been read,
- and a command to be executed connected to
- the newly opened
- .CW data
- file. The code is fairly straightforward: read
- the name of a new directory from standard input
- (we are assuming that the caller of
- .CW newnetcon
- sets up the standard input correctly); then
- write the configuration string (if it is not empty),
- raising an error if the write failed; then run the
- command, attached to the
- .CW data
- file.
- .LP
- We set up the
- .CW $net
- environment variable so that
- the running command knows its network
- context, and can access other files in the
- directory (the
- .CW local
- and
- .CW remote
- files, for example).
- Given
- .CW newnetcon ,
- the implementation of
- .CW dial ,
- .CW announce ,
- and
- .CW listen
- is quite easy:
- .P1
- fn announce {
- (addr cmd) := $*
- (netdir clone addr) := ${cs $addr}
- newnetcon $netdir 'announce '^$addr $cmd <> $clone
- }
- fn dial {
- (addr cmd) := $*
- (netdir clone addr) := ${cs $addr}
- newnetcon $netdir 'connect '^$addr $cmd <> $clone
- }
- fn listen {
- newnetcon ${dirname $net} '' $1 <> $net/listen
- }
- .P2
- .CW Dial
- and
- .CW announce
- differ only in the string that is written to the control
- file;
- .CW listen
- assumes it is being called in the context of
- an
- .CW announce
- command, so can use the value
- of
- .CW $net
- to open the
- .CW listen
- file to wait for incoming connections.
- .LP
- The upshot of these function definitions is that we
- can make connections to, and announce, services
- on the network. The code for a simple client might look like:
- .P1
- dial tcp!somewhere.com!5432 {
- echo connected to `{cat $net/remote}
- echo hello somewhere >[1=0]
- }
- .P2
- A server might look like:
- .P1
- announce tcp!somewhere.com!5432 {
- listen {
- echo got connection from `{cat $net/remote}
- cat
- }
- }
- .P2
- .SH
- .I Sh
- and the windowing environment
- .LP
- The main interface to the Inferno graphics and windowing
- system is a textual one, based on Osterhaut's Tk,
- where commands to manipulate the graphics inside
- windows are strings using a uniform syntax not
- a million miles away from the syntax of
- .I sh .
- (See section 9 of Volume 1 for details).
- The
- .CW tk
- .I sh
- module provides an interface to the Tk graphics
- subsystem, providing not only graphics capabilities,
- but also the channel communication on which
- Inferno's Tk event mechanism is based.
- .LP
- The Tk module gives each window a unique
- numeric id which is used to control that window.
- .P1
- load tk
- wid := ${tk window 'My window'}
- .P2
- loads the tk module, creates a new window titled ``My window''
- and assigns its unique identifier to the variable
- .CW $wid .
- Commands of the form
- .CW "tk $wid"
- .I tkcommand
- can then be used to control graphics in the window.
- When writing tk applets, it is helpful to get feedback
- on errors that occur as tk commands are executed, so
- here's a function that checks for errors, and minimises
- the syntactic overhead of sending a Tk command:
- .P1
- fn x {
- args := $*
- or {tk $wid $args} {
- echo error on tk cmd $"args':' $status
- }
- }
- .P2
- It assumes that
- .CW $wid
- has already been set.
- Using
- .CW x ,
- we could create a button in our new window:
- .P1
- x button .b -text {A button}
- x pack .b -side top
- x update
- .P2
- Note that the nice coincidence of the quoting rules
- of
- .I sh
- and tk mean that the unquoted
- .I sh
- command block argument to the
- .CW button
- command gets through to tk unchanged,
- there to become quoted text.
- .LP
- Once we've got a button, we want to know when
- it has been pressed. Inferno Tk sends events
- through Limbo channels, so the Tk module provides
- access to simple string channels. A channel is
- created with the
- .CW chan
- command.
- .P1
- chan event
- .P2
- creates a channel named
- .CW event .
- A
- .CW send
- command takes a string to send down the channel,
- and the
- .CW ${recv}
- builtin yields a received value. Both operations
- block until the transfer of data can proceed \- as with
- Limbo channels, the operation is synchronous. For example:
- .P1
- send event 'hello, world' &
- echo ${recv event}
- .P2
- will print ``hello, world''. Note that the send
- and receive operations must execute in different
- processes, hence the use of the
- .CW &
- backgrounding operator.
- Although for implementation reasons they are
- part of the Tk module, these channel operations
- are potentially useful in non-graphical scripts \-
- they will still work fine if there's no graphics context.
- .LP
- The
- .CW "tk namechan"
- command makes a channel known to Tk.
- .P1
- tk namechan $wid event
- .P2
- Then we can get events from Tk:
- .P1
- x .b configure -command {send event buttonpressed}
- while {} {echo ${recv event}} &
- .P2
- This starts a background process that prints a message
- each time the button is pressed.
- Interaction with the window manager is handled in
- a similar way. When a window is created, it is automatically
- associated with a channel of the same name as the window id.
- Strings arriving on this are window manager events, such as
- .CW resize
- and
- .CW move .
- These can be interpreted if desired, or forwarded back
- to the window manager for default handling with
- .CW "tk winctl" .
- The following is a useful idiom that does all the usual
- event handling on a window:
- .P1
- while {} {tk winctl $wid ${recv $wid}} &
- .P2
- One thing worth knowing is that the default
- .CW exit
- action (i.e. when the user closes the window) is
- to kill all processes in the current process group, so
- in a script that creates windows,
- it is usual to fork the process group with
- .CW "pctl newgrp"
- early on, otherwise
- it can end up killing the shell window that spawned it.
- .SH
- An example
- .LP
- By way of an example. I'll present a function that implements
- a simple network chat facility, allowing two people on the
- network to send text messages to one another, making use
- of the network functions described earlier.
- .LP
- The core is a function called
- .CW chat
- which assumes that its standard input has
- been directed to an active network connection; it creates a
- window containing an entry widget and a text widget. Any text
- entered into the entry widget is sent to the other end
- of the connection; lines of text arriving from
- the network are appended to the text widget.
- .LP
- The first part of the function creates the window,
- forks the process group, runs the window controller
- and creates the widgets inside the window:
- .P1
- fn chat {
- load tk
- pctl newpgrp
- wid := ${tk window 'Chat'}
- nl := '
- \&' # newline
- while {} {tk winctl $wid ${recv $wid}} &
- x entry .e
- x frame .f
- x scrollbar .f.s -orient vertical -command {.f.t yview}
- x text .f.t -yscrollcommand {.f.s set}
- x pack .f.s -side left -fill y
- x pack .f.t -side top -fill both -expand 1
- x pack .f -side top -fill both -expand 1
- x pack .e -side top -fill x
- x pack propagate . 0
- x bind .e '<Key-'^$nl^'>' {send event enter}
- x update
- chan event
- tk namechan $wid event event
- .P2
- The middle part of
- .CW chat
- loops in the background getting text entered
- by the user and sending it across the network
- (also putting a copy in the local text widget
- so that you can see what you have sent.
- .P1
- while {} {
- {} ${recv event}
- txt := ${tk $wid .e get}
- echo $txt >[1=0]
- x .f.t insert end '''me: '^$txt^$nl
- x .e delete 0 end
- x .f.t see end
- x update
- } &
- .P2
- Note the null command on the second line,
- used to wait for the receive event without
- having to deal with the value (there's only
- one event that can arrive on the channel, and
- we know what it is).
- .LP
- The final piece of
- .CW chat
- gets lines from the network and puts them
- in the text widget. The loop will terminate when
- the connection is dropped by the other party, whereupon
- the window closes and the chat finished:
- .P1
- getlines {
- x .f.t insert end '''you: '^$line^$nl
- x .f.t see end
- x update
- }
- tk winctl $wid exit
- }
- .P2
- Now we can wrap up the network functions and the
- chat function in a shell script, to finish off the little demo:
- .P1
- #!/dis/sh
- .I "Include the earlier function definitions here."
- fn usage {
- echo 'usage: chat [-s] address' >[1=2]
- raise usage
- }
- args=$*
- or {~ $#args 1 2} {usage}
- (addr args) := $*
- if {~ $addr -s} {
- # server
- or {~ $#args 1} {usage}
- (addr nil) := $args
- announce $addr {
- echo announced on `{cat $net/local}
- while {} {
- net := $net
- listen {
- echo got connection from `{cat $net/remote}
- chat &
- }
- }
- }
- } {
- or {~ $#args 0} {usage}
- # client
- dial $addr {
- echo made connection
- chat
- }
- }
- .P2
- If this is placed in an executable script file
- named
- .CW chat ,
- then
- .P1
- chat -s tcp!mymachine.com!5432
- .P2
- would announce a chat server using tcp
- on
- .CW mymachine.com
- (the local machine)
- on port 5432.
- .P1
- chat tcp!mymachine.com!5432
- .P2
- would make a connection to
- the previous server; they would both pop
- up windows and allow text to be typed in from
- either end.
- .SH
- Lexical binding
- .LP
- One potential problem with all this passing around
- of fragments of shell script is the scope of names.
- This piece of code:
- .P1
- fn runit {x := Two; $*}
- x := One
- runit {echo $x}
- .P2
- will print ``Two'', which is quite likely to confound the
- expectations of the person writing the script if they
- did not know that
- .CW runit
- set the value of
- .CW $x
- before calling its argument script.
- Some functional languages (and the
- .I es
- shell) implement
- .I "lexical binding"
- to get around this problem. The idea
- is to derive a new script from the old
- one with all the necessary variables bound to
- their current values, regardless of the context in which
- the script is later called.
- .LP
- .I Sh
- does not provide any explicit support for
- this operation; however it is possible to fake
- up a reasonably passable job.
- Recall that blocks can be treated as strings if necessary,
- and that
- .CW ${quote}
- allows the bundling of lists in such a way that they
- can later be extracted again without loss. These two
- features allow the writing of the following
- .CW let
- function (I have omitted argument checking code here and
- in later code for the sake of brevity):
- .P1
- subfn let {
- # usage: let cmd var...
- (let_cmd let_vars) := $*
- if {~ $#let_cmd 0} {
- echo 'usage: let {cmd} var...' >[1=2]
- raise usage
- }
- let_prefix := ''
- for let_i in $let_vars {
- let_prefix = $let_prefix ^
- ${quote $let_i}^':='^${quote $$let_i}^';'
- }
- result=${parse '{'^$let_prefix^$let_cmd^' $*}'}
- }
- .P2
- .CW Let
- takes a block of code, and the names of environment variables
- to bind onto it; it returns the resulting new block of code.
- For example:
- .P1
- fn runit {x := hello, world; $*}
- x := a 'b c d' 'e'
- runit ${let {echo $x} x}
- .P2
- will print:
- .P1
- a b c d e
- .P2
- Looking at the code it produces is perhaps more
- enlightening than examining the function definition:
- .P1
- x=a 'b c d' 'e'
- echo ${let {echo $x} x}
- .P2
- produces
- .P1
- {x:=a 'b c d' e;{echo $x} $*}
- .P2
- .CW Let
- has bundled up the values of the two bound variables,
- stuck them onto the beginning of the code block
- and surrounded the whole thing in braces.
- It makes sure that it has valid syntax by using
- .CW ${parse} ,
- and it ensures that the correct arguments are
- passed to the script by passing it
- .CW $* .
- .LP
- Note that all the variable names used inside the
- body of
- .CW let
- are prefixed with
- .CW let_ .
- This is to try to reduce the likelihood that someone
- will want to lexically bind to a variable of a name used
- inside
- .CW let .
- .SH
- The module interface
- .PP
- It is not within the scope of this paper to discuss in
- detail the public module interface to the shell, but
- it is probably worth mentioning some of the other
- benefits that
- .I sh
- derives from living within Inferno.
- .PP
- Unlike shells in conventional systems, where
- the shell is a standalone program, accessible
- only through
- .CW exec() ,
- in Inferno,
- .I sh
- presents a module interface that allows programs
- to gain lower level access to the primitives provided
- by the shell. For example, Inferno programs can make use of
- the shell syntax parsing directly, so
- a shell command in a configuration script might be
- checked for correctness before running it,
- or parsed to avoid parsing overhead when running
- a shell command within a loop.
- .PP
- More importantly, as long as it implements a superset
- of the
- .CW Shellbuiltin
- interface, an application can
- load
- .I itself
- into the shell as a module, and define builtin commands
- that directly access functionality that it can provide.
- .PP
- This can, with minimum effort, provide an application
- with a programmable interface to its primitives.
- I have modified the Inferno window manager
- .CW wm ,
- for example, so that instead of using a custom, fairly limited
- format file, its configuration file is just
- a shell script.
- .CW Wm
- loads itself into the shell,
- defines a new builtin command
- .CW menu
- to create items in
- its main menu, and runs a shell script.
- The shell script has the freedom to customise
- menu entries dynamically, to run arbitrary programs,
- and even to publicise this interface to
- .CW wm
- by creating a file with
- .CW file2chan
- and interpreting writes to the file as calls
- to the
- .CW menu
- command:
- .P1
- file2chan /chan/wmmenu {} {menu ${unquote ${rget data}}}
- .P2
- A corresponding
- .CW wmmenu
- shell function might be written to provide access to
- the functionality:
- .P1
- fn wmmenu {
- echo ${quote $*} > /chan/wmmenu
- }
- .P2
- Inferno has blurred the boundaries between
- application and library and
- .I sh
- exploits this \- the possibilities have only just begun
- to be explored.
- .SH
- Discussion
- .LP
- Although it is a newly written shell, the use of tried
- and tested semantics means that most of the
- normal shell functionality works quite smoothly.
- The separation between normal commands and
- substitution builtins is arguable, but I think justifiable.
- The distinction between the two classes of command
- means that there is less awkwardness in the transition between
- ordinary commands and internally implemented commands:
- both return the same kind of thing. A normal command's
- return value remains essentially a simple true/false status,
- whereas the new substitution builtins are returning a list
- with no real distinction between true and false.
- .LP
- I believe that the decision to keep as much functionality as
- possible out
- of the core shell has paid off. Allowing command blocks
- as values enables external modules to define new
- control-flow primitives, which in turn means that
- the core shell can be kept reasonably static,
- while the design of the shell modules evolves
- independently. There is a syntactic price
- to pay for this generality, but I think it is worth it!
- .LP
- There are some aspects to the design that I do not
- find entirely satisfactory. It is strange, given the
- throwaway and non-explicit use of subprocesses
- in the shell, that exceptions do not propagate
- between processes. The model is Limbo's, but
- I am not sure it works perfectly for
- .I sh .
- I feel there should probably be some difference
- between:
- .P1
- raise error > /dev/null
- .P2
- and
- .P1
- status error > /dev/null
- .P2
- The shared nature of loaded modules can cause
- problems; unlike environment variables, which
- are copied for asynchronously running processes,
- the module instances for an asynchronously running
- process remain the same. This means that a
- module such as
- .CW tk
- must maintain mutual exclusion locks to
- protect access to its data structures. This
- could be solved if Limbo had some kind of polymorphic
- type that enabled the shell to hold some data on
- a module's behalf \- it could ask the module
- to copy it when necessary.
- .LP
- One thing that is lost going from Limbo to
- .I sh
- when using the
- .CW tk
- module is the usual reference-counted garbage collection
- of windows. Because a shell-script holds not
- a direct handle on the window, but only a string
- that indirectly refers to a handle held inside
- the
- .CW tk
- module, there is no way for the system to
- know when the window is no longer referred to,
- so, as long as a
- .CW tk
- module is loaded, its windows must be
- explicitly deleted.
- .LP
- The names defined by loaded modules will
- become an issue if
- loaded modules proliferate. It is not easy
- to ensure that a command that you are executing
- is defined by the module you think it is, given name clashes
- between modules.I have been considering some
- kind of scheme that would allow discrimination
- between modules, but for the moment, the point
- is moot \- there are no module name clashes, and
- I hope that that will remain the case.
- .SH
- Credits
- .LP
- .I Sh
- is almost entirely an amalgam of other people's
- ideas that I have been fortunate enough to
- encounter over the years. I hope they will forgive
- me for the corruption I've applied...
- .LP
- I have been a happy user of a version of Tom Duff's
- .I rc
- for ten years or so; without
- .I rc ,
- this shell would not exist in anything like its present form.
- Thanks, Tom.
- .LP
- It was Byron Rakitzis's UNIX version of
- .I rc
- that I was using for most of those ten years; it was his
- version of the grammar that eventually became
- .I sh 's
- grammar, and the name of my
- .CW glom()
- function came straight from his
- .I rc
- source.
- .LP
- From Paul Haahr's
- .I es ,
- a descendent of Byron's
- .I rc ,
- and the shell that probably holds the most in common
- with
- .I sh ,
- I stole the ``blocks as values'' idea;
- the way that blocks transform into strings
- and vice versa is completely
- .I es 's.
- The syntax of the
- .CW if
- command also comes directly from
- .I es .
- .LP
- From Bruce Ellis's
- .I mash ,
- the other programmable shell for Inferno,
- I took the
- .CW load
- command, the
- \f5"{}\fP
- syntax and the
- .CW <>
- redirection operator.
- .LP
- Last, but by no means least, S. R. Bourne,
- the author of the original
- .I sh ,
- the granddaddy of this
- .I sh ,
- is indirectly responsible for all these shells.
- That so much has remained unchanged from
- then is a testament to the power of his original
- vision.
|