Stay Out of Trouble
Now that our scripts are getting a little more complicated, Let's look at
some common mistakes that we might run into. To do this, we'll create the
following script called trouble.bash
. Be sure to
enter it exactly as written.
if
[
$number = "1" ];
then
echo
"Number equals 1"
else
echo
"Number does not equal 1"
fi
When we run this script, it should output the line "Number equals 1"
because, well, number
equals 1. If we don't get the expected
output, we need to check our typing; we've made a mistake.
Empty Variables
Let's edit the script to change line 3 from:
to:
and run the script again. This time we should get the following:
./trouble.bash
/trouble.bash: [: =: unary operator expected.
Number does not equal 1As we can see, bash
displayed an error message
when we ran the script. We might think that by removing the "1" on line 3 it
created a syntax error on line 3, but it didn't. Let's look at the error
message again:
We can see that ./trouble.bash
is reporting the
error and the error has to do with "[
". Remember
that "[
" is an abbreviation for the test
shell builtin. From this we can determine that the
error is occurring on line 5 not line 3.
First, to be clear, there is nothing wrong with line 3. number=
is perfectly good syntax. We sometimes want to set a variable's value to
nothing. We can confirm the validity of this by trying it on the command
line:
number=
[me@linuxbox me]$See, no error message. So what's wrong with line 5? It worked before.
To understand this error, we have to see what the shell sees. Remember that
the shell spends a lot of its life expanding text. In line 5, the shell expands
the value of number
where it sees $number
. In our
first try (when number=1
), the shell substituted 1 for
$number
like so:
if
[
1 = "1" ];
then
However, when we set number to nothing (number=
), the shell saw
this after the expansion:
if
[
= "1" ];
then
which is an error. It also explains the rest of the error message we
received. The "=
" is a binary operator; that is, it
expects two items to operate upon - one on each side. What the shell is trying
to tell us is that there is only one item and there should be a unary operator
(like "!
") that only operates on a single item.
To fix this problem, change line 5 to read:
if
[
"$number" = "1" ];
then
Now when the shell performs the expansion it will see:
if
[
"" = "1" ];
then
which correctly expresses our intent.
This brings up two important things to remember when we are writing scripts. We need to consider what happens if a variable is set to equal nothing and we should always put double quotes around parameters that undergo expansion.
Missing Quotes
Edit line 6 to remove the trailing quote from the end of the line:
echo
"Number equals 1and run the script again. We should get this:
./trouble.bash
./trouble.bash: line 8:
unexpected EOF while looking for matching "
./trouble.bash: line 10 syntax error: unexpected end of fileHere we have another instance of a mistake in one line causing a problem later in the script. What happened in this case was that the shell kept looking for the closing quotation mark to determine where the end of the string is, but ran off the end of the file before it found it.
These errors can be a real pain to track down in a long script. This is one reason we should test our scripts frequently while we are writing so there is less new code to test. Also, using a text editor with syntax highlighting makes these bugs easier to find.
Isolating Problems
Finding bugs in scripts can sometimes be very difficult and frustrating. Here are a couple of techniques that are useful:
Isolate blocks of code by "commenting them out." This trick involves putting comment characters at the beginning of lines of code to stop the shell from reading them. We can do this to a block of code to see if a particular problem goes away. By doing this, we can isolate which part of a program is causing (or not causing) a problem.
For example, when we were looking for our missing quotation we could have done this:
if
[
$number = "1" ];
then
echo
"Number equals 1
#else
# echo "Number does not equal 1"
fi
By commenting out the else
clause and running the
script, we could show that the problem was not in the else
clause even though the error message suggested that it
was.
Use echo commands to verify assumptions. As we gain experience
tracking down bugs, we will discover that bugs are often not where we first
expect to find them. A common problem will be that we will make a false
assumption about the performance of our program. A problem will develop at a
certain point in the program and we assume the problem is there. This is
often incorrect. To combat this, we can place echo
commands in the code while we are debugging, to produce messages that confirm
the program is doing what is expected. There are two kinds of messages that we
can insert.
The first type simply announces that we have reached a certain point in the program. We saw this in our earlier discussion on stubbing. It is useful to know that program flow is happening the way we expect.
The second type displays the value of a variable (or variables) used in a calculation or test. We will often find that a portion of a program will fail because something that we assumed was correct earlier in the program is, in fact, incorrect and is causing our program to fail later on.
Watching Our Script Run
It is possible to have bash
show us what it is
doing when we run our script. To do this, add a "-x
"
to the first line of the script, like this:
Now, when we run the script, bash will display each line (with expansions performed) as it executes it. This technique is called tracing. Here is what it looks like:
./trouble.bash
+ number=1
+ '[' 1 = 1 ']'
+ echo 'Number equals 1'
Number equals 1Alternately, we can use the set
command within the script to turn
tracing on and off. Use set
-x
to turn tracing on and set
+x
to turn tracing off. For example.:
set
-x
if
[
$number = "1" ];
then
echo
"Number equals 1"
else
echo
"Number does not equal 1"
fi
set
+x