Bash Shell Scripting
Note: This is not an exhaustive description of Bash. It barely qualifies as introductory. The amount of information on the Bash shell is extensive.
There is also an expectation that you have some basics of the command line. There is a wonderful, free bit of documentation in html and PDF form.
The Basics
Ubuntu uses the bash
(Bourne Again Shell) as the user default shell. This is the shell the user is deposited into when they login. It is the shell that provides the prompt, accepts commands and then executes them on behalf of the user.
The shell, in addition to executing Linux/Unix commands on behalf of the logged-in user, has its own set of built-in commands. These commands are listed here. The SS64 web site is a good location for documentation as is TLDP (The Linux Documentation Project). There is also an advanced guide here.
Since the shell executes commands one at a time with each prompt, the next logical extension to that is the ability to create shell programs or scripts. Essentially, all of the shell built-in commands as well as the system commands can be arranged like a program. This is essential when there is a sequence of events that have to occur in a specified order or if there is a procedure that is done regularly. Turn the procedure into a script and then either run is manually when needed or automate the process with a program like cron
.
Let’s start with some basics. The shell script needs to have a tag at the top to tell the active shell how to run the program. This tag appears as a comment since it starts with a hash (#), but also contains the program name that should be used to run all the statements that will follow. This special marker must be the first line and begin in the first column. An example is shown below.
[bash] #!/bin/bashecho "Hello world!"
[/bash]
The script indicates that it should be run by the shell /bin/bash
. Let’s say we save those lines of code into a file called hell
o. You run a script simply by typing its name at a shell prompt.
$ hello
When running scripts, they must have execute permissions. If you try to run a script and get a Permission denied error, there’s a good chance the execute permission is not set. This is easily remedied with:
$ chmod u+x hello
Further if you run the command, but instead of permission denied, you get command not found for an error, then you do not have the current directory (.) in your search path. To get around this, you can edit your PATH environment variable (discussed later) or simply alter the way you run the script.
$ ./hello
Variables
Assigning
Variables are simple identifiers of string data. Integer types can also be used, but basically everything is a string. Values are assigned using an equals sign (=) with no spaces in between.
[bash]var=value[/bash]OR
[bash]var="value with spaces"[/bash]OR
[bash]var=’value with spaces'[/bash]OR
[bash]var=value\ with\ spaces[/bash]There are different kinds of quotes which will be discussed below.
Accessing
Accessing the values of variables is done with the dollar sign ($) dereference operator.
[bash] var="value with spaces"echo $var
[/bash]
Concatenating
We can combine strings simply by placing them close together as shown in the next example
[bash] #!/bin/bashvar1=abc
var2=123
var3=xyz
echo -e "var1 = $var1\nvar2 = $var2\nvar3 = $var3"
var4=$var1$var2$var3
echo "var4 = $var4"
[/bash]
The -e
option of echo
simply tells echo
to process the \n
as a newline instead of literal text. The value of var4
is made by concatenating the values of var1
, var2
and var3
.
When using the dereference operator in conjunction with concatenation, you have to be careful of mixing. Consider the following:
[bash] v=playecho $ving
[/bash]
The author wanted to add ing to the end of the value of v
. The shell interpreter read that line as accessing the variable ving
which has no value. Therefore nothing would be displayed except a blank line.
To fix this we use braces ({}) to separate the variable from the string literal to be concatenated.
[bash] v=playecho ${v}ing
[/bash]
Quotes
You may not realize it, but you’ve seen some details above regarding quoting that were not specifically pointed out.
Back Quotes or Back Ticks
These characters allow you to call a command and have the results returned as a string to be assigned to a variable or used as an argument to another command. Consider the following:
[bash] today=`date +’%y%m%d’`echo $today
[/bash]
The use of this kind of quoting has been deprecated and the use of $() should be used instead.
[bash] today=$(date +’%y%m%d’)echo $today
[/bash]
The old method only shown here in the event you should run into old scripts that use it.
Escape Character (Backslash)
The use of the escape character or blackslash (\) is to change the default behavior of a single character. Consider the following:
[bash] echo "That will be $22"[/bash]
The output would be
That will be 2
This is because the dollar sign is the variable access operator and $2 means the second argument passed to this program – which we haven’t discussed yet, but that’s what it is.
To fix this we need to have the dollar sign mean just a dollar sign. The escape character is one way to solve this.
[bash] echo "That will be \$22"[/bash]
Another way to solve this is to read on.
Single Quotes
As we have already seen, variable expansion (using the $ operator) and more will happen inside of double quotes. To keep any of those things from happening and to treat every character as simply a character to be displayed as-is we use single quotes.
[bash] echo ‘That will be $22’echo ‘$var is not expanded and I can call $(somecommand) without anything happening.’
[/bash]
Double Quotes
When using double quotes, everything is printed as-is except $, ` and \. That is, the dollar sign, back tick and backslash retain their special properties as previously explained. So that means that:
[bash] var=textecho "\$var = $var and `date +’%y%m%d’` = $(date +’%y%m%d’)"
[/bash]
Produces:
$var = text and 130312 = 130312
Tests
Truth in shell scripting is based on the return value of commands. A return value of zero (0) indicates success and non-zero means something else happened. Hence, zero mean true and non-zero means false.
There are three forms of testing. They help our programs react through conditional processing and grant us the ability to examine a variety of details.
test
and [ ]
The test
built-in command and the [ ]
operator are identical. They both offer a list of file testing options:
Unary operators -d Is a directory -e File exists -f Is a regular file -h Is a symbolic link (also -L) -r Is readable by you -w Is writable by you -x Is executable by you -s Is not empty Binary operators -nt Test if file1 is newer than file 2 based on modification date -ot Test if file1 is older than file 2 based on modification date -ef Test if file1 is a hard link to file2.
The test built-in can also do arithmetic comparisons:
-eq Is equal -ne Is not equal -lt Is less than -gt Is greater than -le Is less than or equal -ge Is greater than or equal
string comparisons
Binary operators = Is equal (also ==) != Is not equal < Is less than > Is greater than [Note: < and > need to be escaped when using [] since they also represent the redirection operators] Unary operators -n String has a non-zero length -z String has a zero length
and logical operators
-o Logical or -a Logical and
[[ ]]
This test construct is very similar to the [ ]
version. However, filename expansion and word slitting are not processed.
All other aspects are the same and you also gain the ability to use &&
, ||
, <
and >
more freely.
(( ))
This is the test to perform arithmetic expressions. It also has the ability to use the C-style increment (++) and decrement (--) operators. The comparison operators are more rich:
= Is equal (also ==) != Is not equal < Is less than > Is greater than <= Is less than or equal >= Is greater than or equal
Conditionals
Now that we've seen the tests, we should put them to good use. The basic structure of it test is
if condition; then statements fi
OR
if condition then statements fi
OR
if condition; then statements else statements fi
OR
if condition; then statements elif condition; then statements else statements fi
OR
if condition; then statements else if condition; then statements else statements fi fi
As you can see there are several variations to how the if
can actually be constructed.
Here is a script with some standard if-elif-else tests to show both how to construct an if-test and how to use the tests.
Loops
while
The while loop is a top test loop and is written in the form:
while condition; do statements done
OR
while condition do statements done
The following will use an integer variable x
to print the values 0 through 9:
let x=0
while (( x < 10 ))
do
echo $x
(( x++ ))
done
[/bash]
for
The for loop uses lists of strings separated by spaces (quotes are honored) to denote a list. Let's say you wanted to generate some IP addresses in a particular range. Let's say further that we wanted to generate hundreds of insert
statements suitable for a simple MySQL table. We could use two loops - one for and one while in the following manner:
for x in 248 249
do
let y=2
while (( y < 255 ))
do
echo "insert into ip_list values('192.168.$x.$y');"
(( y++ ))
done
done
[/bash]
The above example may raise the question on the further use of quotes. Such as, why are $x
and $y
evaluated within single quotes? The answer is the outer (double) quotes are what matter here. The inner (single) quotes are simply displayed literally as no further quotation processing is done once the outer set has begun. Therefore variable expansion will happen even within the nested set of single quotes.
Here Documents
Command line arguments
Functions