Bash programming is a topic that can
be dealt with in a couple of pages or hundreds of pages. Harold
Rodriguez explains Bash programming in this 2 Part tutorial.
His slick and excellent style of writing has enabled him to
cover all the essential features of bash programing in a few
pages.
If you have never programmed in Bash before, this is the best
place to begin. In case you have a little knowledge of bash,
then too you could have a look.. a lot of interesting scripts
have been explained by Harold.
Introduction
Like all the shells available in Linux,
the Bourne Again SHell is not only an excellent command line
shell, but a scripting language in itself. Shell scripting,
allows you to fully utilize the shell's abilities and to automate
a lot of tasks that would otherwise require a lot of commands
to perform. A lot of programs lying around your Linux box are
shell scripts. If you are interested in learning how they work,
or in modifying them, it is essential that you understand the
bash syntax and semantics. In addition, by understanding the
bash language, you will be able to write your own programs to
do things exactly the way you want them done.
Programming or Scripting?
People who are new to programming are
generally confused as to what the difference is between a programming
and scripting language. Programming languages are generally
a lot more powerful and a lot faster than scripting languages.
Examples of programming languages are C, C++, and Java. Programming
languages generally start from source code (a text file containing
instructions on how the final program is to be run) and are
compiled (built) into an executable. This executable is not
easily ported into different operating systems. For instance,
if you were to write a C program in Linux, you would not be
able to run that C program in a Windows 98 system. In order
to do so, you would have to recompile the source code under
the Windows 98 system. A scripting language also starts from
source code, but is not compiled into an executable. Rather,
an interpreter reads the instructions in the source file and
executes each instruction. Unfortunately, because the interpreter
has to read each instruction, interpreted programs are generally
slower than compiled programs. The main advantage is that you
can easily port the source file to any operating system and
have it interpreted there right on the spot. bash is a scripting
language. It is great for small programs, but if you are planning
on doing major applications, a programming language might be
more beneficial to you. Other examples of scripting languages
are Perl, Lisp, and Tcl.
What do you need to know?
Writing your own shell scripts requires
you to know the very basic everyday Linux commands. For example,
you should know how to copy, move, create new files, etc. The
one thing you must know how to do is to use a text editor. There
are three major text editors in Linux, vi, emacs and pico. If
you are not familiar with vi or emacs, go for pico or some other
easy to use text editor.
Warning!!!!
Do not practice scripting as the root
user! Anything could happen! I will not be held responsible
if you accidentally make a mistake in the coding and screw up
your system. You have been warned! Use a normal user account
with no root privileges. You may even want to create a new user
just for scripting practice. This way the worst thing that can
happen is the user's directory gets blown away.
Your first Bash program
Our first program will be the classical
"Hello World" program. Yes, if you have programmed
before, you must be sick of this by now. However, this is traditional,
and who am I to change tradition? The "Hello World"
program simply prints the words "Hello World" to the
screen. So fire up your text editor, and type the following
inside it:
#!/bin/bash
echo "Hello World" |
The first line tells Linux to use the
bash interpreter to run this script. In this case, bash is in
the /bin directory. If bash is in a different directory on your
system, make the appropriate changes to the line. Explicitly
specifying the interpreter is very important, so be sure you
do it as it tells Linux which interpreter to use to run the
instructions in the script. The next thing to do is to save
the script. We will call it hello.sh. With that done, you need
to make the script executable
$ chmod 700 ./hello.sh
Refer to the manual page for chmod
if you do not know how to change permissions for a file. Once
this is done, you will be able to run your program just by typing
its name:
$ ./hello.sh
Hello World
There it is! Your first program! Boring
and useless as it is, this is how everyone starts out. Just
remember the process here. Write the code, save the file, and
make it executable with chmod.
Commands, Commands and Commands
What exactly did your first program
do? It printed the words "Hello World" to the screen.
But how did it do that? It used commands. The only line of code
you wrote in the program was echo "Hello World". Well,
which one is the command? echo. The echo program takes one argument
and prints that argument to the screen.
An argument is anything that follows
after you type the program name. In this case, "Hello World"
was the argument that you passed to echo When you type the command
ls /home/root, the argument to ls is /home/root. So what does
this all mean? It means that if you have a program that takes
an argument and prints it to the screen, you can use that instead
of using echo. Let us assume that we have a program called foo
This program will take a single argument, a string of words,
and print them to the screen. We can rewrite our program as
such
#!/bin/bash
foo "Hello World" |
Save it and chmod it and then run it
$ ./hello
Hello World
The exact same result. Was there any
unique code at all? No. Did you really write anything? Not unless
you are the author of the echo program. All you did, was to
embed the echo program into your shell program, with a argument
already given. A real world example of an alternative to the
echo command is the printf command. printf offers more control,
especially if you are familiar with C programming. In fact,
the exact same result could have been done without making a
shell program
$ echo
"Hello World"
Hello World
bash shell scripting offers a wide
variety of control and is easy to learn. As you have just seen,
you use Linux commands to write your shell programs with. Your
shell program is a collection of other programs, specially put
together to perform a task.
A More Useful Program
We will write a program that will move
all files into a directory, and then delete the directory along
with its contents, and then recreate the directory. This can
be done with the following commands
$ mkdir
trash
$ mv * trash
$ rm -rf trash
$ mkdir trash
Instead of having to type all that interactively on the shell,
write a shell program instead
|
#!/bin/bash
mkdir trash
mv * trash
rm -rf trash
mkdir trash
echo "Deleted all files!"
|
Save it as clean.sh and now all you
have to do is to run clean.sh and it moves all files to a directory,
deletes them, recreates the directory, and even prints a message
telling you that it successfully deleted all files. So remember,
if you find that you are doing something that takes a while
to type over and over again, consider automating it with a shell
program.
Comments
Comments help to make your code more
readable. They do not affect the output of your program. They
are specially made for you to read. All comments in bash begin
with the hash symbol: "#", except for the first line
(#!/bin/bash). The first line is not a comment. Any lines after
the first line that begin with a "#" is a comment.
Take the following piece of code
|
#!/bin/bash
# this program counts from 1 to 10:
for i in 1 2 3 4 5 6 7 8 9 10; do
echo $i
done
|
Even if you do not know bash scripting, you immediately know
what the above program does, because of the comment. It is good
practice to make use of comments. You will find that if you
need to maintain your programs in the future, having comments
will make things easier.
Variables
Variables are basically "boxes"
that hold values. You will want to create variables for many
reasons. You will need it to hold user input, arguments, or
numerical values. Take for instance the following piece of code
|
#!/bin/bash
x=12
echo "The value of variable x is $x"
|
What you have done here, is to give x the value of 12. The line
echo "The value of variable x is $x" prints the current
value of x. When you define a variable, it must not have any
whitespace in between the assignment operator: "=".
Here is the syntax
variable_name=this_value
The values of variables can be accessed
by prefixing the variable name with a dollar symbol: "$".
As in the above, we access the value of x by using echo $x.
There are two types of variables. Local
variables, and environmental variables. Environmental variables
are set by the system and can usually be found by using the
env command. Environmental variables hold special values. For
instance, if you type
$ echo $SHELL
/bin/bash
You get the name of the shell you are
currently running. Environmental variables are defined in /etc/profile
and ~/.bash_profile. The echo command is good for checking the
current value of a variable, environmental, or local.
Note
: Setting of
Environmental variables are explained in detail in Article
No. 30 - How to set Shell Environment Variables .
The article also explains
some features of the bash shell.
If you are still having problems understanding why we need variables,
here is a good example
|
#!/bin/bash
echo "The value of x is 12."
echo "I have 12 pencils."
echo "He told me that the value of x is 12."
echo "I am 12 years old."
echo "How come the value of x is 12?"
|
Okay, now suppose you decide that you
want the value of x to be 8 instead of 12. What do you do? You
have to change all the lines of code where it says that x is
12. But wait... there are other lines of code with the number
12. Should you change those too? No, because they are not associated
with x. Confusing right? Now, here is the same example, only
it is using variables
|
#!/bin/bash
x=12 # assign the value 12 to variable x
echo "The value of x is $x."
echo "I have 12 pencils."
echo "He told me that the value of x is $x."
echo "I am 12 years old." echo "How come
the value of x is $x?"
|
Here, we see that $x will print the
current value of variable x, which is 12. So now, if you wanted
to change the value of x to 8, all you have to do, is to change
the line x=12 to x=8, and the program will automatically change
all the lines with $x to show 8, instead of 12. The other lines
will be unaffected. Variables have other important uses as well,
as you will see later on.
Control Structures
Control structures allow your program
to make decisions and to make them more compact. More importantly
as well, it allows us to check for errors. So far, all we have
done is write programs that start from the top, and go all the
way to the bottom until there are no more commands left in the
program to run. For instance
|
#!/bin/bash
cp /etc/foo .
echo "Done."
|
This little shell program, call it
bar.sh, copies a file called /etc/foo into the current directory
and prints "Done" to the screen. This program will
work, under one condition. You must have a file called /etc/foo.
Otherwise here is what happens
$ ./bar.sh
cp: /etc/foo: No such file or directory
Done.
So you can see, there is a problem. Not everyone who runs your
program will have /etc/foo in their system. It would perhaps
be better if your program checked if /etc/foo existed, and then
if it did, it would proceed with the copying, otherwise, it
would quit. In pseudo code, this is what it would look like
if /etc/code exists, then
copy /etc/code to the current directory
print "Done." to the screen.
otherwise,
print "This file does not exist."
to the screen
exit |
Can this be done in bash? Of course!
The collection of bash control structures are, if, while, until,
for and case. Each structure is paired, meaning it starts with
a starting "tag" and ends with an ending "tag".
For instance, the if structure starts with if, and ends with
fi. Control structures are not programs found in your system.
They are a built in feature of bash. Meaning that from here
on, you will be writing your own code, and not just embedding
programs into your shell program.
if ... else ... elif ... fi
One of the most common structures is
the if structure. This allows your program to make decisions,
like, "do this if this conditions exists, else, do something
else". To use the if structure effectively, we must make
use of the test command. test checks for conditions, that is,
existing files, permissions, or similarities and differences.
Here is a rewrite on bar.sh
|
#!/bin/bash
if test -f /etc/foo
then
# file exists, so copy and print a message.
cp /etc/foo .
echo "Done."
else
# file does NOT exist, so we print a message
and exit.
echo "This file does not exist."
exit
fi
|
Notice how we indent lines after then
and else. Indenting is optional, but it makes reading the code
much easier in a sense that we know which lines are executed
under which condition. Now run the program. If you have /etc/foo,
then it will copy the file, otherwise, it will print an error
message. test checks to see if the file /etc/foo exists. The
-f checks to see if the argument is a regular file. Here is
a list of test's options
|
Command Line
Parameters for ' test '
|
|
-d
|
check
if the file is a directory |
|
-e
|
check if the file
exists |
|
-f
|
check if the file
is a regular file |
|
-g
|
check if the file
has SGID permissions |
|
-r
|
check if the file
is readable |
|
-s
|
check if the file's
size is not 0 |
|
-u
|
check if the file
has SUID permissions |
|
-w
|
check if the file
is writetable |
|
-x
|
check if the file
is executable |
else is used when you want your program
to do something else if the first condition is not met. There
is also the elif which can be used in place of another if within
the if. Basically elif stands for "else if". You use
it when the first condition is not met, and you want to test
another condition.
If you find that you are uncomfortable
with the format of the if and test structure, that is
if test
-f /etc/foo
then
then, you can do it like this
if [ -f /etc/foo ]; then
The square brackets form test. If you
have experience in C programming, this syntax might be more
comfortable. Notice that there has to be white space surrounding
both square brackets. The semicolon: ";" tells the
shell that this is the end of the command. Anything after the
semicolon will be run as though it is on a separate line. This
makes it easier to read basically, and is of course, optional.
If you prefer, just put then on the next line.
When using variables with test, it
is a good idea to have them surrounded with quotation marks.
Example:
if [ "$name" -eq 5 ];
then
the -eq
operator will be explained later on in this article.
while ... do ... done
The while structure is a looping structure.
Basically what it does is, "while this condition is true,
do this until the condition is no longer true". Let us
look at an example
|
#!/bin/bash
while true; do
echo "Press CTRL-C to quit."
done
|
true is actually a program. What this
program does is continuously loop over and over without stopping.
Using true is considered to be slow because your shell program
has to call it up first and then run it. You can use an alternative,
the ":" command
#!/bin/bash
while :; do
echo "Press CTRL-C to quit."
done |
This achieves the exact same thing,
but is faster because it is a built in feature in bash. The
only difference is you sacrifice readability for speed. Use
whichever one you feel more comfortable with. Here is perhaps,
a much more useful example, using variables
|
#!/bin/bash
x=0; # initialize x to 0
while [ "$x" -le 10 ]; do
echo "Current value of x: $x"
# increment the value of x:
x=$(expr $x + 1)
sleep 1
done
|
As you can see, we are making use of
the test (in its square bracket form) here to check the condition
of the variable x. The option -le checks to see if x is less
than, or equal to the value 10. In English, the code above says,
"While x is less than 10 or equal to 10, print the current
value of x, and then add 1 to the current value of x.".
sleep 1 is just to get the program to pause for one second.
You can remove it if you want. As you can see, what we were
doing here was testing for equality. Check if a variable equals
a certain value, and if it does, act accordingly. Here is a
list of equality tests
|
Checks
equality between numbers
|
|
x -eq y
|
Check is x is equal to y |
|
x -ne y
|
Check if x is not equal to y |
|
x -gt y
|
Check if x is greater than y |
|
x -lt y
|
Check if x is less than y |
|
Checks
equality between strings
|
|
x = y
|
Check if x is the
same as y |
|
x != y
|
Check if x is not the same as
y |
|
-n x
|
Evaluates to true if x is not
null |
|
-z x
|
Evaluates to true if x is null |
The above looping script we wrote
should not be hard to understand, except maybe for this line
x=$(expr $x + 1)
The comment above it tells us that
it increments x by 1. But what does $(...) mean? Is it a variable?
No. In fact, it is a way of telling the shell that you want
to run the command expr $x + 1, and assign its result to x.
Any command enclosed in $(...) will be run
|
#!/bin/bash
me=$(whoami)
echo "I am $me."
|
Try it and you will understand what
I mean. The above code could have been written as follows with
equivalent results
|
#!/bin/bash
echo "I am $(whoami)."
|
You decide which one is easier for
you to read. There is another way to run commands or to give
variables the result of a command. This will be explained later
on. For now, use $(...).
until ... do ... done
The until structure is very similar
to the while structure. The only difference is that the condition
is reversed. The while structure loops while the condition is
true. The until structure loops until the condition is true.
So basically it is "until this condition is true, do this".
Here is an example
|
#!/bin/bash
x=0
until [ "$x" -ge 10 ]; do
echo "Current value of x: $x"
x=$(expr $x + 1)
sleep 1
done
|
This piece of code may look familiar.
Try it out and see what it does. Basically, until will continue
looping until x is either greater than, or equal to 10. When
it reaches the value 10, the loop will stop. Therefore, the
last value printed for x will be 9.
for ... in ... do ... done
The for structure is used when you
are looping through a range of variables. For instance, you
can write up a small program that prints 10 dots each second
|
#!/bin/bash
echo -n "Checking system for errors"
for dots in 1 2 3 4 5 6 7 8 9 10; do
echo -n "."
done
echo "System clean."
|
In case you do not know, the -n option
to echo prevents a new line from automatically being added.
Try it once with the -n option, and then once without to see
what I mean. The variable dots loops through values 1 to 10,
and prints a dot at each value. Try this example to see what
I mean by the variable looping through the values
|
#!/bin/bash
for x in paper pencil pen; do
echo "The value of variable x is: $x"
sleep 1
done
|
When you run the program, you see that
x will first hold the value paper, and then it will go to the
next value, pencil, and then to the next value, pen. When it
finds no more values, the loop ends.
Here is a much more useful example.
The following program adds a .html extension to all files in
the current directory
|
#!/bin/bash
for file in *; do
echo "Adding .html extension to $file..."
mv $file $file.html
sleep 1
done
|
If you do not know, * is a wild card
character. It means, "everything in the current directory",
which is in this case, all the files in the current directory.
All files in the current directory are then given a .html extension.
Recall that variable file will loop through all the values,
in this case, the files in the current directory. mv is then
used to rename the value of variable file with a .html extension.
case ... in ... esac
The case structure is very similar
to the if structure. Basically it is great for times where there
are a lot of conditions to be checked, and you do not want to
have to use if over and over again. Take the following piece
of code
|
#!/bin/bash
x=5 # initialize x to 5
# now check the value of x:
case $x in
0) echo "Value of x is 0."
;;
5) echo "Value of x is 5."
;;
9) echo "Value of x is 9."
;;
*) echo "Unrecognized value."
esac
|
The case structure will check the value
of x against 3 possibilities. In this case, it will first check
if x has the value of 0, and then check if the value is 5, and
then check if the value is 9. Finally, if all the checks fail,
it will produce a message, "Unrecognized value.".
Remember that "*" means "everything", and
in this case, "any other value other than what was specified".
If x holds any other value other than 0, 5, or 9, then this
value falls into the *'s category. When using case, each condition
must be ended with two semicolons. Why bother using case when
you can use if? Here is the equivalent program, written with
if. See which one is faster to write, and easier to read
|
#!/bin/bash
x=5 # initialize x to 5
if [ "$x" -eq 0 ]; then
echo "Value of x is 0."
elif [ "$x" -eq 5 ]; then
echo "Value of x is 5."
elif [ "$x" -eq 9 ]; then
echo "Value of x is 9."
else
echo "Unrecognized value."
fi
|
Quotation Marks
Quotation marks play a big part in
shell scripting. There are three types of quotation marks. They
are the double quote: ", the forward quote: ', and the
back quote: `. Does each of them mean something? Yes.
Note :
Article
No. 26 - Wildcards, Quotes, Back Quotes, Apostrophes in shell
commands ( * ? [] " ` ') deals with special
characters in great detail. Please refer to that article in
case you are not familiar with the use of these special characters
in shell commands. Below is a quick explanation of some of those
features
The double quote is used mainly to
hold a string of words and preserve whitespace. For instance,
"This string contains whitespace.". A string enclosed
in double quotes is treated as one argument. For instance, take
the following examples
$ mkdir
hello world
$ ls -F
hello/ world/
Here we created two directories. mkdir
took the strings hello and world as two arguments, and thus
created two directories. Now, what happens when you do this:
$ mkdir
"hello world"
$ ls -F
hello/ hello world/ world/
It created a directory with two words.
The quotation marks made two words, into one argument. Without
the quotation marks, mkdir would think that hello was the first
argument, and world, the second.
Forward quotes are used primarily to
deal with variables. If a variable is enclosed in double quotes,
its value will be evaluated. If it is enclosed in forward quotes,
its value will not be evaluated. To make this clearer, try the
following example
|
#!/bin/bash
x=5 # initialize x to 5
# use double quotes
echo "Using double quotes, the value of x is: $x"
# use forward quotes
echo 'Using forward quotes, the value of x is: $x'
|
See the difference? You can use double
quotes if you do not plan on using variables for the string
you wish to enclose. In case you are wondering, yes, forward
quotes can be used to preserve whitespace just like double quotes
$ mkdir
'hello world'
$ ls -F
hello world/
Back quotes are completely different
from double and forward quotes. They are not used to preserve
whitespace. If you recall, earlier on, we used this line
x=$(expr $x + 1)
As you already know, the result of
the command expr $x + 1 is assigned to variable x. The exact
same result can be achieved with back quotes:
x=`expr $x + 1`
Which one should you use? Whichever
one you prefer. You will find the back quote used more often
than the $(...). However, I find $(...) easier to read, especially
if you have something like this
|
$!/bin/bash
echo "I am `whoami`"
|
This was just the beginning. You will
learn lots of more concepts in the concluding part of this article.
Till then happy shell scripting..
About the Author - Harold Rodriguez
( xconsole [at] it.yorku.ca ) maintains a site called Moonfrog.
It has many useful tutorials on various topics. You could
access his site at http://it.yorku.ca/moonfrog
|
Note
: Please replace the [at] with a @ in the email
id of the author.
|
|

|
©
Copyright by Harold Rodriguez. All rights reserved. Contact
the author for permissions.
|