Introduction to bash scripts

While playing with some extra features on my newly purchased QNAP TS-210 at some point I was forced to finally write some bash script to be able to control things, that are uncontrollable via administration panel. For this reason I had to learn myself at least basics of bash, that is installed on my QNAP. I used floppix page and linuxconfig.org Wiki as a base for my learning process (and source for this article), however I had to write down my own memo-list to be able to quickly find, what I’m looking for.

The really, really basics

Bash? Bash scripts?

You may skip this chapter, if you know at least anything about bash scripts.

A bash script is a file containing a list of commands to be executed by the shell. These scripts usually contain a set of commands that you would normally enter from the keyboard. However, more advanced scripts can use conditional flows, loops, procedures etc., to execute certain portions of bash script file only in certain circumstances.

First example:

[code language=”shell”]
#! /bin/bash
# script to turn the screen blue
setterm -background blue
echo It is a blue day
[/code]

First line specifies which shell should be used to interpret the commands in following script (see below). Second line is an example of a comment (it has no effect when the script is executed). Every line starting with # is ignored by the interpreter. Third line sets background colour and fourth line displays a message.

Shebang

Even though you use # to denote lines with comment, that should be completely ignored by bash interpreter, meaning of #! put in first line of each script (obligatory) is different. This is something called shebang and it tells shell exactly which bash interpreter should be used to interpret (execute) following lines of bash script. This is because there may be one or another or many together bash interpreters installed in each Unix-like system.

This, when executing any script for the first time on particular machine or system, you need to find out what is your bash interpreter and where it is located. Enter the following into your command line:

[code language=”shell”]
$ which bash
[/code]

Whatever result of executing above command will be is your shebang and must be pust as a first line of each of your scripts.

Running bash scripts

Before you can execute any bash script you must save it to a file and make that file executable by executing:

[code language=”shell”]
$ chmod +x colorme.sh
[/code]

from command line.

Bash script extension can be optionally omitted (see below).

As for executing your first bahs script, try the following:

  1. Make the script executable: chmod 700 colorme.
  2. Try to run the script by typing: colorme.
  3. You will get an error message: command not found.
  4. Now, try to run the script with command: ./colorme.
  5. You should see desired effect (i.e. background color changed to blue).

Remember, that any Unix-like system will only only look for commands or scripts in the directories in your search path. Thus, in first case (point 2 above) system searches and tries to run colorme command in the directories /usr/bin and /bin. Fails to find it there and thus returns the error message.

When you type ./colorme, then system will understand this as: Run colorme from the current directory.

Debugging bash scripts

If you are getting error messages when you run the script, you can trace the lines as they execute, by using bash -v colorme, i.e. preceding script name with -v parameters, which stands for verbose (be as detailed as possible). After your script executes in this mode, each line is displayed on the screen so that you know exactly what your script is doing (and on what it is failing).

Executing system commands

Here is an example of how to run system command in bash script and the simplest backup script out there:

[code language=”shell”]
#!/bin/bash
tar -czf homedirectory.tar.gz /home/linuxconfig
[/code]

It does nothing else except for compressing contents of given directory into given file. However, we’ll use this script in later examples.

Quotes and quotations

Quotations and quotes are important part of bash and bash scripting. Using quotes let you use literal, meaningless characters to print some static text with variables to print some variable text.

Single quotes

All characters will be read literally inside single quotes meaning, that if you put any variable name inside, it will be printed without evaluating it, i.e. you’ll see just variable name, not its value:

[code language=”shell”]
#!/bin/bash
BASHVAR="Bash Script"

echo ‘This is the value: $BASHVAR’
[/code]

It is not possible to use another single quote within two single quotes not even if the single quote is escaped by backslash.

Double Quotes

All characters, except for “$”, “” and backticks, will be read literally inside double quotes meaning, that if you put any variable name inside, it will be printed with evaluating it, i.e. you’ll variable’s value, not its name:

[code language=”shell”]
#!/bin/bash
BASHVAR="Bash Script"

echo "This is the value: $BASHVAR"
[/code]

Compare results of executing these two scripts above to catch the difference.

Bacticks

This special kind of quotation marks that allows you to mix static text with an effect of execution of system commands:

[code language=”shell”]
#!/bin/bash
BASHVAR="Bash Script"

echo "It’s $BASHVAR and "$BASHVAR" using backticks: `date`"
[/code]

Variables

Usage

Variables are created when you assign a value to them (COLOR=blue). Value stored in variable can be later used. You “call” variable by putting $ before its name (echo $COLOR).

To test this, modify above script like that:

[code language=”shell”]
#!/bin/bash
COLOR=blue
setterm -background $COLOR
echo It is a $COLOR day
[/code]

Above example is actually bad one, because it omits quotation marks around string-like value. Refer to above section about quotation marks for details.

In above example you’re assigning a value to a variable and then immediately use it. This is a dummy example, actually never used in real world. However, you can use variables in a much better way, to get user input, i.e. to parametrize way how your script is working by basing it on values user has entered on-screen or as parameters during script execution. Or to catch some system variables like current date. See following examples and chapter for details.

Here is a bit better example of our previous backup script:

[code language=”shell”]
#!/bin/bash
OF=myhome_directory_$(date +%Y%m%d).tar.gz
tar -czf $OF /home/linuxconfig
[/code]

This time we’re using system date to create new backup archive with different file name each time backup script is executed.

Getting user input

A script can get input from the user while it is running. Use the echo command to display a prompt on the screen and the read command to get the input:

[code language=”shell”]
#! /bin/bash
echo -n "Pick a screen color (blue, yellow, red): "
read -e COLOR
setterm -background $COLOR
echo It is a $COLOR day
[/code]

Instead of assigning a particular variable you can read user input into default build-in variable $REPLY:

[code language=”shell”]
#!/bin/bash
echo -e "How do you feel about bash scripting? "
read
echo "You said $REPLY, I’m glad to hear that! "
[/code]

Or you can read all member answers (responses, values, variables) into array at once:

[code language=”shell”]
#!/bin/bash
echo -e "What are your favorite colours ? "
read -a colours
echo "My favorite colours are also ${colours[0]}, ${colours[1]} and ${colours[2]}"
[/code]

Instead of asking user to provide parameters on-screen you can read execution parameters. This is exactly the same, as when debugging your bash script (see above), where in bash -v colorme, the bash is a script name and -v colorme are two parameters to parametrize execution of this script.

Bash will accept up to nine parameters separated by spaces. And it will automatically create corresponding variables for them, that you can use in your script. The first parameter is $1, the second parameter is $2 and so on. The colorme script using input parameters is shown below:

[code language=”shell”]
#! /bin/bash
setterm -background $1
echo It is a $1 day
[/code]

Try to run the script as colorme red. In this case, the $1 variable will be given the value red. Run the script again using colorme blue command, in which case the $1 variable will have the blue value.

We can also store arguments from bash command line in special array (we’ll discuss arrays in details later). For example:

[code language=”shell”]
args=("$@")
#echo arguments to the shell
echo ${args[0]} ${args[1]} ${args[2]}
[/code]

Global vs. local variables

Each bash script can use functions, which we’ll discuss later. At this stage all we need to know, is that each function is a part of code that is being executed at requested moments in main script and that each flow (both main script’s flow and each function’s flow) uses different set
of variables.

For example:

[code language=”shell”]
#!/bin/bash
VAR="global variable"
function bash {
local VAR="local variable"
echo $VAR
}
echo $VAR
bash
echo $VAR
[/code]

An effect of running this code should be:

[code language=”shell”]
global variable
local variable
global variable
[/code]

This means that both main script’s flow and bash function’s flow store different value for variable even though variable name is the same in both cases. This also means that executing function does not “overwrite” variable value, because these are actually two different values.

If you have problems getting this, then imagine (but only imagine, never do this in code), that each variable defined inside any function is names like functionname_variablename). And thus, in above example you have two, completely separate variables, one named VAR and other named BASH_VAR.

Conditionals

The if / else / fi statements

Conditionals in bash are one of the most important language constructs (as good as in nearly any other programming language) since they control flow of script execution and can execute or halt execution of certain script parts in given conditions.

Please note the spacing inside the [ and ] brackets! Without the spaces, this won’t work!

[code language=”shell”]
#!/bin/bash
directory="./BashScripting"
if [ -d $directory ]; then
echo "Directory exists"
else
echo "Directory does not exists"
fi
[/code]

The -d element (and -eq if following examples) are bash operators and will be discussed in next chapter.

The if / else / elif / fi checking

Here is an extended version of above script, where we’re also using elif to declare alternative conditional scenario:

[code language=”shell”]
#!/bin/bash
NUM1=2
NUM2=1
if [ $NUM1 -eq $NUM2 ]; then
echo "Both Values are equal"
elif [ $NUM1 -gt $NUM2 ]; then
echo "NUM1 is greater then NUM2"
else
echo "NUM2 is greater then NUM1"
fi
[/code]

The case / esac checking

If there are many conditional scenarios, then using many elif constructs could produce hard to read code. We can make it a little bit easier to read by using case statement conditional:

[code language=”shell”]
#!/bin/bash
echo "What is your preferred programming / scripting language"
echo "1) bash"
echo "2) perl"
echo "3) phyton"
echo "4) c++"
echo "5) I do not know !"
read case;
case $case in
1) echo "You selected bash";;
2) echo "You selected perl";;
3) echo "You selected phyton";;
4) echo "You selected c++";;
5) exit
esac
[/code]

Nested if / else checking

You can nest if and else on many levels to produce a really rockin conditionals. I.e.:

[code language=”shell”]
#!/bin/bash
choice=4

echo "1. Bash"
echo "2. Scripting"
echo "3. Tutorial"
echo -n "Please choose a word [1,2 or 3]? "

while [ $choice -eq 4 ]; do

read choice

if [ $choice -eq 1 ] ; then

echo "You have chosen word: Bash"

else

if [ $choice -eq 2 ] ; then

echo "You have chosen word: Scripting"

else

if [ $choice -eq 3 ] ; then

echo "You have chosen word: Tutorial"

else

echo "Please make a choice between 1-3 !"
echo "1. Bash"
echo "2. Scripting"
echo "3. Tutorial"
echo -n "Please choose a word [1,2 or 3]? "
choice=4

fi
fi
fi
done
[/code]

Comparisons

Arithmetic comparisons

Here are bash arithmetic comparison operators and their counterparts in mathematics:

[code language=”shell”]
-lt < (lesser than)
-gt > (bigger than)
-le <= (lesser than or equal)
-ge >= (bigger than or equal)
-eq == (equal)
-ne != (not equal)
[/code]

Here goes ans example with simple integers comparison:

[code language=”shell”]
NUM1=2
NUM2=2
if [ $NUM1 -eq $NUM2 ]; then
echo "Both Values are equal"
else
echo "Values are NOT equal"
fi
[/code]

Play with values of variables declared in the beginning of above script and observe how effects changes.

And here is another example, this time with elif statement, which should already be known to you:

[code language=”shell”]
#!/bin/bash
NUM1=2
NUM2=1
if [ $NUM1 -eq $NUM2 ]; then
echo "Both Values are equal"
elif [ $NUM1 -gt $NUM2 ]; then
echo "NUM1 is greater then NUM2"
else
echo "NUM2 is greater then NUM1"
fi
[/code]

String comparisons

Let’s start with string operators list:

[code language=”shell”]
= (equal)
!= (not equal)
-n s1 (string s1 is not empty)
-z s1 (string s1 is empty)
[/code]

And some nifty example:

[code language=”shell”]
#!/bin/bash
S1="Bash"
S2="Scripting"
if [ $S1 = $S2 ]; then
echo "Both Strings are equal"
else
echo "Strings are NOT equal"
fi
[/code]

File testing operators

Finally we reach long list of comparison operators that can be cast on filename or directory name or path to perform certain file-level operations and checkings:

[code language=”shell”]
-d directoryname (check if directory exists)
-e filename (check if file exists)
-f filename (check if file is really a file and not a directory)
-s filename (check if file size is non-zero)
-S filename (check if file is a socket)
-L filename (check if file is a symbolic link)
-r filename (check if file is readable)
-w filename (check if file is writable)
-x filename (check if file is executable)
[/code]

Here is an example, quite similar to previous, where we were checking, if directory exists.

[code language=”shell”]
#!/bin/bash
file="./file"
if [ -e $file ]; then
echo "File exists"
else
echo "File does not exists"
fi
[/code]

We can use a loop to let script continue, but sleep as long as file does not exist. We’re creating a nifty kind of file watchdog:

[code language=”shell”]
#!/bin/bash
while [ ! -e myfile ]; do
sleep 1
# Do something…
done
[/code]

This script will continue (and perform some kind of operation) only, if required file appears. Note negator operator of ! which negates the -e operator.

Leave a Reply