📜 ⬆️ ⬇️

Inferno Shell

FAQ: What is OS Inferno and why is it needed?

Inferno OS shell for many years has caused me exclusively negative emotions. And I never understood that in Inferno sh causes delight in some people. But, as they say, it's better late than never - today I decided to thoroughly deal with the shell, and as a result, I also got through - this is really a unique thing! Incredibly elegant and simple.

I will begin all the same with shortcomings, for greater objectivity. The main one is the shell is very slow. It is not known why, but all the inferno shells (there are actually several of them, but now we are talking about /dis/sh.dis ) are very slow. This is extremely strange, because usually the speed of applications written in Limbo (in JIT mode) is between the speed of C and fast scripting languages ​​like Perl. Therefore, full-fledged applications on sh will not work. But, nevertheless, for every sneeze, uncovering Limbo is also inconvenient, so all sorts of starting scripts and other small things anyway have to be written on sh . The second serious drawback - the inconvenience of using the text console, the lack of command history, autocompletion, easy editing when entering is very annoying (but I was just told about the rlwrap utility, running emu through rlwrap -a seems to solve this problem). The third - the syntax of this shell is unusual using unmatched quotes, which, when trying to highlight the syntax of its scripts using the highlight for absolutely any other shell (due to the lack of a ready highlight for infernovskogo) leads to a complete nightmare. I was going to solve this problem today by implementing syntax highlighting for vim, for which I sat down to deal with the shell ... and as a result, instead of syntax highlighting, I could not resist and write this article. :)

Content



')

From what, from what, from what are our scripts made?


The functionality supported by /dis/sh.dis out of the box is impressive! There is not even conditional operators and cycles! No features. And then what is there, and how can one exist under these conditions? Now see. So what is:
Probably, having seen the last item you thought “Aha! Any special commands. Surely everything else they do, everything is trite. "... but no, you did not guess. Of the built-in commands, three are usually used: exit , run and load ; unquote used commands for working with strings are quote and unquote . As for run , it simply executes the specified script in the current shell (analog . Or source in bash).

But load - yes, this is a bomb! It allows loading additional modules into the current shell that are written in Limbo and allow you to add absolutely any functionality to the shell - if, for, functions, exceptions, regular expressions, mathematical operations, etc. etc. There is also the unload command that allows you to unload these modules dynamically. :) Nevertheless, even without loadable modules, the shell is absolutely complete and functional - which I will prove at the end of the article by implementing on “bare sh” if and for!

Oh, something else I forgot to mention. (Do you think now it’s definitely “aha!”? Nah.) He still supports the comments. Beginning with # . :)

Running applications


Much identical to any * nix shell:

 ; echo one two | wc 1 2 8 

But there are differences.

Additional command I / O redirects

The last feature is especially interesting. For example, the standard command for comparing two cmp files with its help can compare not the files, but the results of the work of two other commands: cmp <{ ls /dir1 } <{ ls /dir2 } .

$ status

Inferno, an unusual approach to the implementation of the exit code (exit status) of the application. In traditional OS, any program ends with a number: 0 if everything is in order, and any other in case of an error. In inferno, any program either just terminates (if everything is all right) or it generates an exception, the value of which is a string with the error text.

An agreement has been adopted whereby this line should begin with “fail:” if this is a regular error - i.e. the application is not “dropped”, but just wants to exit by returning this error to the one who launched this application (usually this is the shell). If the error does not begin with “fail:”, then after the application is completed, its process will remain in memory (in the “broken” state) so that it can be examined by debuggers and find out why this exception occurred (analogous to core dumps, only instead of saving they hang on the disk in memory).

So, after the completion of the running command, its “exit code” - i.e. the text of the error-exception will be in the $status environment variable (the prefix “fail:” will be automatically deleted from it, if the exception text except “fail:” contained nothing more, then $status will contain the string “failed”).

Lines, lists of lines, blocks of commands


Infern shell operates only with strings and lists of strings.

Line

A string is either a word that does not contain spaces and some special characters, or any characters enclosed in single quotes. The only “escaped” character in such a string is the single quote itself, and it is written with two such quotes:
 ; echo 'quote '' <-- here' quote ' <-- here 

No \ n, \ t, variable interpolations, etc. in rows is not supported. If you need to insert a newline character in the string, either simply insert it as is (by pressing Enter), or concatenate a string with a variable containing this character. Inferno, there is a unicode (1) utility that can output any character by its code, which is done with its help (the construction "{} is described below, but for now consider it an analogue `` bash-a):
 ; cr="{unicode -t 0D} ; lf="{unicode -t 0A} ; tab="{unicode -t 09} ; echo -n 'a'$cr$lf'b'$tab'c' | xd -1x 0000000 61 0d 0a 62 09 63 0000006 

Double quotes for strings are not used (by the way, paired double quotes in the Infern shell are not used for anything at all).

Block of commands

A block of commands is written inside curly brackets, and the whole block is processed by the shell as one line (including the characters of curly brackets).
 ; { echo one; echo two } one two ; '{ echo one; echo two }' one two 

The only difference from a string in single quotes is that sh checks the syntax of command blocks and reformats them into more compact strings.
 ; echo { cmd | } sh: stdin: parse error: syntax error ; echo { cmd | cmd } {cmd|cmd} ; echo { echo one echo two } {echo one;echo two} 


List of strings

Lists of lines is just zero and more lines separated by whitespace. They can be enclosed in parentheses, but in most cases this is not necessary.

Any shell command is just a list of lines , where the first line contains a command or a block of code, and the remaining lines are parameters for them.
 ; echo one two three one two three ; echo (one two) (three) one two three ; (echo () (one (two three))) one two three ; ({echo Hello, $1!} World) Hello, World! 

To concatenate lists of strings, use the ^ operator. It can be applied to two lists containing either the same number of elements (then elements are concatenated in pairs), or one of the lists must contain only one element, then it will be concatenated with each element of the second list. As a special case, if both lists contain only one element at a time, the usual concatenation of two strings is obtained.
 ; echo (abc) ^ (1 2 3) a1 b2 c3 ; echo (ab) ^ 1 a1 b1 ; echo 1 ^ (ab) 1a 1b ; echo a ^ b ab 

Usually, one shell command must be written on one line - there are no special characters to mean “continue the command on the next line”. But it is very simple to split a command into several lines without opening the line and not closing the line (with a single quote) or a block of commands (curly bracket) or a list of lines (parentheses), or terminating the previous line with a concatenation operator.

Environment variables


Working with variables in the Infern shell looks deceptively similar to traditional shells, but do not be fooled by this!
 ; a = 'World' ; echo Hello, $a! Hello, World! 


/ env

Environment variables in Inferno OS are implemented differently than in traditional OS. Instead of introducing an additional POSIX API for working with environment variables and passing them to each process through a nightmare like int execvpe(const char *file, char *const argv[], char *const envp[]); environment variables are just files in the /env directory. This gives interesting possibilities, for example, an application can provide access to its environment variables by other applications over the network — simply by exporting (via listen (1) and export (4) ) your /env .

You can create / delete / modify environment variables by creating, deleting and modifying files in /env . But it should be borne in mind that the current sh keeps a copy of all environment variables in memory, so changes made through the files in /env will see the applications being launched, but not the current sh. On the other hand, by changing the variables in the usual way for the shell (through the operator = ) you automatically update the files in /env .

Shell variable names can contain any characters, including those that are not valid for file names (for example, names . , .. and containing / ). Variables with such names will be available only in the current sh, and launched applications will not see them.

Lists

Another important difference is that all variables contain only lists of strings . You cannot put a single line in a variable - it will always be a list of one line.

Saving an empty list to a variable (explicitly by assigning a=() or implicitly through a= ) is the deletion of the variable.

 ; var = 'first str' second ; echo $var first str second ; echo $#var 2 ; echo $"var first str second 

As you can see, the output of $var and $"var not visually different, because echo displays all its parameters separated by a space, and $" also unites all the elements of a variable through a space. But the first echo command received two parameters, and the last one.

Whenever you implicitly concatenate the value of a variable with something, the shell inserts the concatenation operator ^ (which, as you remember, works with lists of strings, not strings). This is convenient, although unaccustomed to it may be unexpected:
 ; flags = abc ; files = file1 file2 ; echo -$flags $files.b -a -b -c file1.b file2.b ; echo { echo -$flags $files.b } {echo -^$flags $files^.b} 

Assigning variables to the list also works. If in the right part there is a list of more elements than variables in the left part - the last variable will receive the rest of the list, and in the previous variables there will be one element in each.
 ; list = abcd ; (head tail) = $list ; echo $head a ; echo $tail bcd ; (xy) = (3 5) ; (xy) = ($y $x) ; echo 'x='^$x 'y='^$y x=5 y=3 

The list of all script parameters or any code block is in the $* variable, plus individual parameters are in the $1 , $2 , ... variables.

Scopes

Each block of code forms its own scope. Values ​​of variables can be assigned with the operators = and := . The first will change the value of an existing variable or create a new variable in the current scope. The second one always creates a new variable, overlapping the old value of the variable with that name (if it existed) to the end of the current block.
 ; a = 1 ; { a = 2 ; echo $a } 2 ; echo $a 2 ; { a := 3; echo $a } 3 ; echo $a 2 ; { b := 4; echo $b } 4 ; echo b is $b b is 


Variable references

The variable name is just a string after the $ character. And it can be any string - in single quotes or another variable.
 ; 'var' = 10 ; echo $var 10 ; ref = 'var' ; echo $$ref 10 ; echo $'var' 10 ; echo $('va' ^ r) 10 


Intercepted command output


In general, this topic is rather related to the section describing the launch of commands, but in order to understand what we were talking about, it was necessary to first describe the work with lists of strings, so we had to postpone it until the current moment.

Meet the same unmatched quotes that always break syntax highlighting.
As an example, load the file list via the ls call. Given that file names may contain spaces, the standard `{} behavior doesn’t work for us - we only need to separate list items by line breaks.
 ; ifs := "{unicode -t 0A} ; files := `{ ls / } ; echo $#files 82 ; ls / | wc -l 82 

But in general this is done easier and more reliably through the file name patterns, which are deployed by the shell to the file name list:
 ; files2 := /* ; echo $#files2 82 


Embedded commands


There are two types of built-in commands: regular and for working with strings.

Normal commands are invoked in the same way as applications / scripts are run:
 ; run /lib/sh/profile ; load std ; unload std ; exit 

Commands for working with strings are called via ${ } and return (do not output to stdout, namely, return - as it does, for example, referring to the value of a variable) a regular string - i.e. they should be used in the parameters of the usual commands or in the right part of the assignment of values ​​into variables. For example, the command ${quote} escapes the list of lines passed to it into one line, and ${unquote} performs the inverse operation turning one line into a list of lines.
 ; list = 'ab' cd ; echo $list abcd ; echo ${quote $list} 'ab' cd ; echo ${unquote ${quote $list}} abcd 


Add if, for, functions


As I promised, I show how to make my implementations of these badly needed things on “bare sh”. Of course, in real life, this is not required, the std loadable module provides everything that is needed, and it is much more convenient to use it. But, nevertheless, this implementation is of interest as a demonstration of the possibilities of “bare sh”.

Everything is implemented using only:


We do our “functions”

As I already mentioned, any shell command is just a list of lines, where the first line is a command, and the remaining lines are its parameters. A shell command block is just a string, and it can be stored in a variable. And the parameters any block of commands gets in $* , $1 , etc.
 ; hello = { echo Hello, $1! } ; $hello World Hello, World! 

Moreover, we can even do currying of functions in the best spirit of functional programming. :)
 ; greet = {echo $1, $2!} ; hi = $greet Hi ; hello = $greet Hello ; $hi World Hi, World! ; $hello World Hello, World! 

Another example - you can use block parameters to get the desired list item by number:
 ; list = abcdef ; { echo $3 } $list c 


Make your for

I didn’t try to do a full-fledged convenient if I was interested in implementing for, and if I did a minimally functional one. it was necessary for for (the cycle must be stopped once, and without if it is difficult to do this).
 ; do = { $* } ; dont = { } ; if = { (cmd cond) := $* { $2 $cmd } $cond $do $dont } ; for = { (var in list) := $* code := $$#* $iter $var $code $list } ; iter = { (var code list) := $* (cur list) := $list (next unused) := $list $if {$var=$cur; $code; $iter $var $code $list} $next } ; $for i in 10 20 30 { echo i is $i } i is 10 i is 20 i is 30 ; 


Interesting stuff


By default, the shell when the script is run forks the namespace, so the script cannot change the namespace of the process that started it. This is not suitable for scripts whose task is just setting up the parent's namespace. Such scripts should begin with #!/dis/sh -n .

The loaded builtin command lists all the builtin commands from all loaded modules. And whatis built-in command displays information on variables, functions, commands, etc.

If you create the $autoload variable with the list of shell modules, then these modules will be automatically loaded into every shell that is started.

There is syntax sugar support in the shell: && and || . These operators are available in “bare sh”, but are converted to a call to the builtin commands and and or , which are not in “bare sh”, they are from the std module (so that using && and || possible only after load std ).
 ; echo { cmd && cmd } {and {cmd} {cmd}} ; echo { cmd || cmd } {or {cmd} {cmd}} 

Applications written in Limbo can be obtained, for example, when a command line parameter is run, a line with a block of sh commands can be executed very simply using the sh (2) module.

The style of the shell with variables and lists of strings leads to the fact that, unlike traditional shells, in inferno it is necessary to think about screening parameters and values ​​of variables only once - when they are first mentioned. And then they can be transferred anywhere and somehow without thinking about what characters are in them.

Summary


This short article is an almost complete reference guide for the Infern shell. In a sense, the entire functionality of the basic shell is described, and in some detail with all the nuances and examples. sh(1) , , $apid , $prompt , - , sh ., .

/, :
! «» , , if for ( , raise std — .. /bin/false :) run ).

, . , sh-std(1) :
, !

Source: https://habr.com/ru/post/161859/


All Articles