A bunch of things about shell scripting i always forget. Writing it down helps me to keep that in mind.
SH vs Bash
Bash (Bourne-again shell) is basically a superset of SH (Bourne shell) and has more functionality (e.g. arrays). On a few systems the sh
command is just symlink to bash. But bash behaves more POSIX compliant when it’s called via sh
. Look also at this Stackoverflow post.
SH scripting
The sh scripting language is very simple. Almost everything in sh is a command or a symlink to a command. There are variables, conditionals, loops and functions. You can type commands directly in your terminal or put it in a file. The script can then be executed with sh file.sh
.
You can also put a shebang on the first line #!/bin/sh
and give execution rights to the file chmod +x file.sh
. Now you can execute your script with ./file.sh
.
Variables
In sh you can define variables in the scope of a script like this: FOO=1
. If you want them persist over a terminal session you can type export FOO=1
. To return their values type echo $FOO
.
#!/bin/sh
FOO=foo
${BAR:-bar} # default value "bar" for BAR, can be set from outside the script
echo $FOO $BAR # prints: foo bar
echo ${FOO}baz # prints foobaz
echo ${FOO} # prints string length of "foo"
${BAZ:?variable BAZ missing} # exits script if variable BAZ is missing
echo $BAZ
To execute the script above prepend a BAR=blabla
in front of script.sh
or the script exits with message “variable BAR missing”.
Type env
or export -p
into your terminal to see all environment variables.
Parameters
Commandline arguments you can use inside a script. Example shows the execution of a script foo.sh
with arguments one two
. The output will remain the same dispite executing with sh foo.sh one two
or ./foo.sh one two
except $0
.
#!/bin/sh
echo Scriptname: $0
echo Number of args: $#
echo All args: $@
echo All args expanded: "$@"
echo First arg: $1
echo Second arg: $2
echo Process number: $$
echo Last command exit code: $?
sh -stfu 2> /dev/null # silent stderr
echo Last command exit code: $?
Output:
Scriptname: ./foo.sh
Number of args: 2
All args: one two
All args expanded: one two
First arg: one
Second arg: two
Process number: 15442
Last command exit code: 0
Last command exit code: 2
Conditional - the test
command
The sh if
takes a command as parameter, e.g. if ls; then ...; fi
. If the command exits with 0
the condition is true
. To check a condition we have a progam called test
. test 2 -lt 3
would exit with code 0
because 2 is lower then 3. With this program we can check conditions in a if statement:
if test 2 -lt 3
then echo "2<3"
fi
There’s also the shorthand [
for test
. [
is a simple symlink to test
. When test
gets called via [
it expects a closing ]
. Now we can write scripts like:
if [ 2 -lt 3 ]
then echo "2<3"
fi
Following example shows a few capabilities of the test
command. For more see the wikipedia article. Execute the script with ./foo.sh 5
:
#!/bin/sh
# if/else
if [ $1 -le 2 ]
then echo "$1 <= 2"
elif [ $1 -le 4 ]
then echo "$1 <= 4"
else
echo "i can't count to $1"
fi
# numbers
if [ 1 -eq "1" ]; then echo "1 == 1"; fi # equal
if [ 1 -ne 2 ]; then echo "1 != 2"; fi # not equal
if [ 2 -gt 1 ]; then echo "2 > 1"; fi # greater then
if [ 2 -ge 2 ]; then echo "2 >= 2"; fi # greater equal
if [ 1 -lt 2 ]; then echo "1 < 2"; fi # lower then
if [ 1 -le 1 ]; then echo "1 <= 2"; fi # lower equal
# strings
if [ "foo" = "foo" ]; then echo '"foo" == "foo"'; fi
if [ "foo" != "bar" ]; then echo '"foo" != "bar"'; fi
if [ -z "" ]; then echo '"" is empty'; fi
if [ -n "foo" ]; then echo '"foo" is not empty'; fi
# always use "$VAR" in quotes if you checking string variables
if [ "$1" = "5" ]; then echo "$1 == 5"; else echo "$1 != 5"; fi
# check folders and files
if [ -f "$0" ]; then echo "me exists"; fi
mkdir foo
if [ -d "./foo" ]; then echo "folder exists"; fi
rm -r foo
if [ ! -d "./foo" ]; then echo "folder removed"; fi
Loops
Expected! There are loops in sh. Nice, let’s see how they work.
#!/bin/sh
# while loop with break condition
i=0
while [ $i -le 10 ]
do
echo $i
i=$(( $i + 1 ))
done
# infinite loop
while [ : ]
do
echo 1
break # we don't want you to run infinite :)
done
# for loop with list [one, two, three]
for i in one two three
do
echo $i
done
# for loop with list from command (all files/dirs in $PWD that end with .sh)
for i in $(ls *.sh)
do
echo $i
done
# loop through all file names in $PWD that end with .sh
for i in *.sh
do
echo $i
done
# loop through all command line arguments
for i in $@
do
echo $i
done
# same as above, but shorter
for i
do
echo $i
done
Functions
To avoid writing repetitive tasks there are functions in sh. Function arguments are not taken via foo(1, "two")
, instead functions work like normal sh commands, parameters are passed via $@
, $1
, $2
, etc.
#!/bin/sh
hello() {
echo "hello $@"
}
hello world !