The Shell Command Language
The shell operates according to the following general steps:
Shell
(1))Any text below is superceded by the formal grammar defined in the formal grammar section.
This section describes the general tokens the language accepts, it should be noted that due to nature of the language, some tokens are valid only in a specific context.
String of characters that are not Special or Syntactic Elements
String of characters containing at least one of *?
in bareword position
Any sequence of characters between two single quotes ('
)
Any sequence of Double Quoted String Part tokens:
Any sequence of Identifier characters, or a Special Variable follwing a $
Any expression following a $
that is not a variable reference:
$
:$
Any two expressions joined by the Join operator ( [whitespace]), or a variable reference referring to a list value
(
and )
)Any text following a #
in bareword position, up to but not including a newline
The following tokens:
for
in command name positionin
as a syntactic element of a for
expressionAny of the following:
;
in bareword position\\n
(a newline) in bareword position(){}
*?
not in glob positionAny initial path segment starting with the character ~
in bareword position, Optionally followed by a bareword for the username
The shell can create various redirections to file descriptors of a command before executing it, the general syntax for redirections is an optional file descriptor, followed by a redirection operator, followed by a destination.
There are four redirection operators corresponding to various file descriptor open modes: Read
, Write
, WriteAppend
and ReadWrite
, respectively <
, >
, >>
and <>
.
A special syntactic element &fd
can reference a file descriptor as a destination.
Redirections take two main forms, Read/Write redirections, and fd closure redirections.
Write
(>
)&-
# Redirect the standard error to a file, and close the standard input
$ 2> foo 1>&-
# Redirect a file as read-write into the standard input
$ 1<>foo
# Redirect the standard output to /dev/null
$ >/dev/null
The shell performs various expansions, in different stages.
Glob Expansion: Globs shall be expanded to a list.
Variable Expansion: Variables shall be expanded preserving their types.
Juxtaposition Expansion: Juxtapositions shall be expanded as list products.
Other expansions: Tildes, Evaluate expressions, etc. shall be expanded as needed.
Any two expressions joined without any operator are considered to be in a Juxtaposition, with the resulting value being the list product of two expressions.
For instance, (1 2)(3 4)
shall be evaluated to (13 14 23 24)
by calculating the list product of the two expressions (1 2)
and (3 4)
.
Any bareword starting with a tilde (~
) and spanning up to the first path separator (/
) - or EOL - is considered to be a tilde expansion with the text between the tilde and the separator being the username, which shall be expanded to a single string containing the home directory of the given username (or the current user if no username is provided).
Evaluate expressions take the general form of a dollar sign ($
) followed by some expression, which is evaluated by the rules below.
IFS
(or the default splitter \n
(newline, 0x0a)). It should be noted that the shell option inline_exec_keep_empty_segments
will determine whether empty segments in the split list shall be preserved when this expression is evaluated, this behaviour is disabled by default.A Command
is a single simple command, containing arguments and redirections for a single program. The shell can evaluate a sequence of commands, a conditional relation between commands, or various semantic elements composed of commands and intrinsics.
Commands can be either calls to Shell builtins, or external programs.
The commands can be composed into semantic elements, producing composite commands:
A sequence of commands, executed serially independent of each other: Commanad ; Command ; Command ...
It should be noted that a newline (\\n
) can be substituted for the semicolon (;
).
# Do one thing, then do another
echo foo; echo bar
A sequence of commands whose execution depends somehow on the result of another
Command && Command && Command ...
(AND)Short-circuiting command evaluations, will cancel the entire chain should any command fails (have a non-zero exit code)
Command || Command || Command ...
(OR)Short-circuiting command evaluation, will continue down the chain if any command fails.
It should be noted that And
chains bind more tightly than Or
chains, so an expression of the form C1 && C2 || C3
is understood as "evaluate C1
, if successful, evaluate C2
, if not successful, evaluate C3
".
# Create file if not found
test -f foo.txt || touch foo.txt
# Announce execution status of a command
rm test && echo "deleted!" || echo "failed with $?"
For Loops evaluate a sequence of commands once per element in a given list.
The shell has two forms of for loops, one with an explicitly named iteration variable, and one with an implicitly named one.
The general syntax follows the form for name in expr { sequence }
, and allows omitting the name in
part to implicitly name the variable it
.
A for-loop evaluates the sequence once per every element in the expr, seetting the local variable name to the element being processed.
The Shell shall cancel the for loop if two consecutive commands are interrupted via any of SIGINT (\^C), SIGQUIT (\^\) or SIGKILL.
# Iterate over every non-hidden file in the current directory, and prepend '1-' to its name.
$ for * { mv $it 1-$it }
# Iterate over a sequence and write each element to a file
$ for i in $(seq 1 100) { echo $i >> foo }
toplevel :: sequence?
sequence :: variable_decls? or_logical_sequence terminator sequence
| variable_decls? or_logical_sequence '&' sequence
| variable_decls? control_structure terminator sequence
| variable_decls? or_logical_sequence
| variable_decls? terminator sequence
or_logical_sequence :: and_logical_sequence '|' '|' and_logical_sequence
| and_logical_sequence
and_logical_sequence :: pipe_sequence '&' '&' and_logical_sequence
| pipe_sequence
terminator :: ';'
| '\n'
variable_decls :: identifier '=' expression (' '+ variable_decls)? ' '*
| identifier '=' '(' pipe_sequence ')' (' '+ variable_decls)? ' '*
pipe_sequence :: command '|' pipe_sequence
| command
control_structure :: for_loop
for_loop :: 'for' ws+ (identifier ' '+ 'in' ws*)? expression ws+ '{' toplevel '}'
command :: redirection command
| list_expression command?
redirection :: number? '>'{1,2} ' '* string_composite
| number? '<' ' '* string_composite
| number? '>' '&' number
| number? '>' '&' '-'
list_expression :: ' '* expression (' '+ list_expression)?
expression :: evaluate expression?
| string_composite expression?
| comment expession?
| '(' list_expression ')' expression?
evaluate :: '$' '(' pipe_sequence ')'
| '$' expression {eval / dynamic resolve}
string_composite :: string string_composite?
| variable string_composite?
| bareword string_composite?
| glob string_composite?
string :: '"' dquoted_string_inner '"'
| "'" [^']* "'"
dquoted_string_inner :: '\' . dquoted_string_inner? {concat}
| variable dquoted_string_inner? {compose}
| . dquoted_string_inner?
| '\' 'x' digit digit dquoted_string_inner?
| '\' [abefrn] dquoted_string_inner?
variable :: '$' identifier
| '$' '$'
| '$' '?'
| '$' '*'
| '$' '#'
| ...
comment :: '#' [^\n]*
bareword :: [^"'*$&#|()[\]{} ?;<>] bareword?
| '\' [^"'*$&#|()[\]{} ?;<>] bareword?
bareword_with_tilde_expansion :: '~' bareword?
glob :: [*?] bareword?
| bareword [*?]
digit :: <native hex digit>
number :: <number in base 10>
identifier :: <string of word characters>