If you see something wrong, stupid, not detailed enough, bogus, whatever - feel free to change it, it's a wiki (user registration needed - just to prevent SPAM).
Bash builds it features on top of a few basic grammar rules. The code you see everywhere, the code you use, is based on these rules. However, this is a very theoretical view, but if you're interested, it may help you to understand why things look like they look.
In the following examples, some commands will be used. If you don't know these commands, just believe the short explanation. Here, they are only used as examples.
Bash manual says:
A simple command is a sequence of optional variable assignments followed by blank-separated words and redirections, and terminated by a control operator. The first word specifies the command to be executed, and is passed as argument zero. The remaining words are passed as arguments to the invoked command.
Sounds harder than it actually is. It is what you do day-per-day. You enter simple commands with parameters and the shell executes them.
Every more complex operation in Bash can be finally splitted into executing such simple commands.
ls ls -l LC_ALL=C ls
The last one might not be familiar to you. That one simply adds ”LC_ALL=C” to the environment of the ls-program. It doesn't affect your current shell (LC variables are used for localization - later, it was just as example).
Every command has a so-called exit code - it's a kind of return status. The shell can catch it and react on it. It goes from 0 to 255, where 0 means success, and the rest means any kind of failure or issue to report back to the caller.
Note: The simple command construct is the base for all higher constructs. Everything you execute, form pipelines to functions, finally ends up in (many) simple commands. That's why Bash only has one method to finally expand and execute a simple command.
Missing an additional extra article about pipelines and pipelining
[time [-p]] [ ! ] command [ | command2 … ]
Don't get confused about the name “pipeline” here, it's a grammar name for that construct, such a pipeline isn't necessarily a pair of commands where stdout/stdin is connected through a real pipe.
Basically, pipelines are one or more simple commands separated by the |-symbol, and thus, connecting their input and output, for example:
ls /etc | wc -l
will execute ls on /etc and pipe the output to wc, which will count lines. That way, you simply count your directory entries in /etc.
The last command in the pipeline will also set the exit code for the pipeline. This exit code can be “reversed” by preceeding an exclamation-mark to the pipeline: An unsuccessful pipeline will end up “successful” and vice versa.
In this example, the commands in the if-structure will be executed, if the pattern “^root:” is not found in /etc/passwd:
if ! grep '^root:' /etc/passwd; then echo "No root-user defined... eh?" fi
Yes, this is also a pipeline (though there is no piping!), because the exclamation mark to reverse the exit code can only be used in pipelines.
If grep's exit code is 1 (FALSE) (the text was not found), the leading ! will “reverse” the exit code for the shell, the shell sees (and reacts on) exit code 0 (TRUE) and the then-path of the if-clause is executed. One could say we checked for ”not grep “^root” /etc/passwd”.
Another thing you can do with pipelines is logging their execution time. Note that time is not a command, it belongs to the special words for pipeline-syntax:
# time updatedb real 3m21.288s user 0m3.114s sys 0m4.744s
Missing an additional extra article about the list operators
A list is a sequence of one or more pipelines separated by one of the operators ;, &, &&, or ││, and optionally terminated by one of ;, &, or <newline>.
⇒ It's a bunch of pipelines separated or terminated by tokens that all have different meanings for Bash.
Your whole Bash script technically is one big single list!
| Operator | Description |
|---|---|
<PIPELINE1> <newline> <PIPELINE2> | Newlines completely separate pipelines. The next pipeline is simply executed, without any checks or specials (Hey! You do that every day! You enter a command and press <RETURN>!) |
<PIPELINE1> ; <PIPELINE2> | The semicolon does what <newline> does: It completely separates the pipelines |
<PIPELINE> & <PIPELINE> | The pipeline infront of that & is executed async (“in background”) - if a pipeline follows this, it is immediately executed after the async one was started |
<PIPELINE1> && <PIPELINE2> | <PIPELINE1> is executed and only if its exit code was 0 (TRUE), then <PIPELINE2> is executed (AND-List) |
<PIPELINE1> || <PIPELINE2> | <PIPELINE1> is executed and only if its exit code was not 0 (FALSE), then <PIPELINE2> is executed (OR-List) |
Note: POSIX calls this construct a “compound lists”.
See also the list of compound commands.
There are two forms of compound commands:
Basically, it's everything else that's not described elsewhere in this article. See the following table for a short overview (without details - really just a plain overview!):
| Compound command syntax | Description |
|---|---|
( <LIST> ) | Execute <LIST> in an extra subshell ⇒ article |
{ <LIST> ; } | Execute <LIST> as separate group (but not in a subshell) ⇒ article |
(( <EXPRESSION> )) | Evaluate the arithmetic expression <EXPRESSION> ⇒ article |
[[ <EXPRESSION> ]] | Evaluate the conditional expression <EXPRESSION> (aka “the new test command”) ⇒ article |
for <NAME> in <WORDS> ; do <LIST> ; done | Executes <LIST> while setting the variable <NAME> to one of <WORDS> on every iteration (classic for-loop) ⇒ article |
for (( <EXPR1> ; <EXPR2> ; <EXPR3> )) ; do <LIST> ; done | C-style for-loop (driven by arithmetic expressions) ⇒ article |
select <NAME> in <WORDS> ; do <LIST> ; done | Providing simple menus ⇒ article |
case <WORD> in <PATTERN>) <LIST> ;; … esac | Decicions based on pattern matching - executing <LIST> on match ⇒ article |
if <LIST> ; then <LIST> ; else <LIST> ; fi | The if-clause: making decisions based on exit codes ⇒ article |
while <LIST1> ; do <LIST2> ; done | Execute <LIST2> while <LIST1> returns TRUE (exit code) ⇒ article |
until <LIST1> ; do <LIST2> ; done | Execute <LIST2> until <LIST1> returns TRUE (exit code) ⇒ article |
Missing an additional extra article about shell functions
A shell function definition basically makes a compound command available under a new name. The speciality now is, that function, when ran, has its own “private” set of positional parameters and I/O descriptors. It acts like a script in the script. Simple said: You create a new command.
The definition is easy (one of more possibilities):
<NAME> () <COMPOUND_COMMAND> <REDIRECTIONS>
which usually is used with the {…; } compound command, and thus looks like
print_help() { echo "Sorry, no help available"; }
Like told above, a function definition can have any compound command as body. Structures like
countme() for ((x=1;x<=9;x++)); do echo $x; done
are unusual, but perfectly valid since the for-loop construct is a compound command!
If there are redirections specified, these are not performed on function definition, they are performed on function execution:
# this will NOT perform the redirection (on definition time)
f() { echo ok ; } > file
# NOW the redirection will be performed (during EXECUTION of the function)
f
Bash allows three equivalent forms of the function definition:
NAME () <COMPOUND_COMMAND> <REDIRECTIONS> function NAME () <COMPOUND_COMMAND> <REDIRECTIONS> function NAME <COMPOUND_COMMAND> <REDIRECTIONS>
The space between NAME and () is optional, usually you just see it without.
Note: The form function NAME () (COMMANDS IN A SUBSHELL) doesn't work, for parsing reasons (syntax error).
I suggest to use the first form. It's specified in POSIX and all Boune-like shells seem to support it.
Note: Before version 2.05-alpha1, Bash only recognized the definition using curly braces (name() { … }), also, other shells allow the definition using any command (not only compound command set).
To execute a funtion like a regular shell script you would put it together like this:
#!/bin/bash
# Add shebang
mycmd()
{
# this $1 is the one of the function!
find / -iname "$1"
}
# this $1 is the one of the script itself!
mycmd "$1" # Execute command immediately after defining function
exit 0
Just informational:
Internally, for forking, Bash stores the function definitions in environment variables. Variables with the content ”() ….”.
Something like the following works without “officially” declaring a function:
$ export testfn="() { echo test; }"
$ bash -c testfn
test
$
Not much of correct definitions, just some short slogans:
more…
A (very
) simple command
echo "Hello world..."
A common compound command
if [ -d /data/mp3 ]; then cp mymusic.mp3 /data/mp3 fi
if-clauseLet's reverse the exit code of the test command, only one thing changes:
if ! [ -d /data/mp3 ]; then cp mymusic.mp3 /data/mp3 fi