====== Interpreter ======
===== Code interpretion =====
//Bash// scripts are not compiled into executable binary files, but are rather fed into the ''bash'' interpreter which reads the script character by character from the input it is given. The input is parsed and tokenized, i.e. commands and control structures seperated from data, and finally executed. This character -based approach to interpreting the given input has the upside of having a small memory footprint, but the downside of being susceptible to code injection.
Example:
#!/bin/bash
echo "This line will be only printed once!"
padded=$(printf '%*s' $(sed -e 's/^[[:space:]]*//' "$0" | wc -c) ' ' | cat - "$0")
echo "$padded" > "$0"
When executed with:
user@host:~$ ./code_injection.sh
This line will be only printed once!
This line will be only printed once!
This line will be only printed once!
[...]
the above script rewrites itself, shifting the start of the script content to the position after the last previous content. Since the interpreter is currently at this position, it reads and executes the script from there again and again and again.
===== Command groups =====
//Bash// script code can use //command groups// to encapsulate any number of commands to be read and executed as a single unit. There are two seperate ways of //command groups//:
* ''( cmd list )'' executes the commands in the list in a subshell, causing variable assignments not to be retained.
* ''{ cmd list; }'' executes the commands in the list in the current shell.
Example:
#!/bin/bash
{
echo "This line will be only printed once!"
padded=$(printf '%*s' $(sed -e 's/^[[:space:]]*//' "$0" | wc -c) ' ' | cat - "$0")
echo "$padded" > "$0"
exit 0
}
When executed with:
user@host:~$ ./code_injection.sh
This line will be only printed once!
user@host:~$
the above script rewrites itself, shifting the start of the script content to the position after the last previous content.
===== Functions =====
Besides the previously mentioned //command groups//, //Bash// //functions// also encapsulate any number of commands which are read and executed as a single unit. In conjunction with //command groups//, //Bash// //functions// can be used to saveguard //Bash// scripts:
#!/bin/bash
main() {
echo "This line will be only printed once!"
return 0
}
{
main "$@"
exit "$?"
}
Functions in //Bash// do not use arguments like in other programming languages, but rather use positional parameters (e.g. ''$1'', ''$2'', ''$3'' and so on). In order to increase the readablility of the code, an assignment of a functions positional parameters to sensibly named variables in the functions local scope should be done at the start of a function:
#!/bin/bash
set -u
send_email() {
local address="$1"
local subject="$2"
local text="$3"
echo "Sending mail with text:"
echo " ${text}"
echo "and subject:"
echo " ${subject}"
echo "to recipient:"
echo " ${address}"
return 0
}
{
send_email "foo.bar@baz.org" "Test subject" "Hello World!"
exit "$?"
}
===== Variables =====
==== Variable scope ====
Variables in //Bash// are dynamically scoped, which means any variable *not* declared with ''local'' or ''declare'' is declared in the *global* variable scope and is thus available anywhere including in functions which inherit from the global variable scope.
In //Bash// the use of undeclared variables is reported as an error, when the shell is started with the ''-u'' option or the //Bash// option is set within a script with the ''set -u'' //Bash// builtin. For more options, see ''man bash-builtins'' and its section for the ''set'' command.
==== Return code masking ====
When simultaniously declaring variables and assigning values to them special care must be taken, when the value to be assigned originates from a subshell (i.e. a command or a function) and the return code (''$?'') of the assignment operation shall be evaluated. //Bash// does all substitutions (i.e. a command or a function) first and the assignment in a second step. Checking the return code (''$?'') will in such a case always yield the value of the return code of the second step only. E.g.:
#!/bin/bash
set -u
file_contains() {
local file="$1"
local searchword="$2"
local result=$(grep -F "${searchword}" "${file}")
if [[ $? != 0 ]]; then
# Nothing found
return 1
fi
return 0
}
{
file_contains "/etc/hosts" "foo"
exit "$?"
}
When executed with:
user@host:~$ ./return_code_masking.sh; echo $?
0
the script will return ''0'' instead of the expected ''1''. In this case seperating the variable declaration from the value assignment provides the correct result:
[...]
local result
result=$(grep -F "${searchword}" "${file}")
[...]