CIS120Linux Fundementals
Troubleshooting Bash Scripts
As our scripts become more complex, it's crucial to address what happens when things go wrong. This chapter examines common errors in scripts and techniques to track down and eradicate these issues.
Syntactic Errors
One general class of errors is syntactic. These involve mistyping elements of shell syntax, causing the shell to stop executing the script. Here's an example script to demonstrate common types of errors:
#!/bin/bash
# trouble: script to demonstrate common errors
number=1
if [ $number = 1 ]; then
echo "Number is equal to 1."
else
echo "Number is not equal to 1."
fi
As written, this script runs successfully:
[me@linuxbox ~]$ trouble
Number is equal to 1.
Missing Quotes
Let's edit the script by removing the trailing quote from the argument following the first echo
command:
#!/bin/bash
# trouble: script to demonstrate common errors
number=1
if [ $number = 1 ]; then
echo "Number is equal to 1.
else
echo "Number is not equal to 1."
fi
Running the script results in:
[me@linuxbox ~]$ trouble
/home/me/bin/trouble: line 10: unexpected EOF while looking for matching `"' /home/me/bin/trouble: line 13: syntax error: unexpected end of file
The line numbers reported by the error messages are not where the missing quote was removed but much later in the
program. The shell continues looking for the closing quote until it finds one, immediately after the second
echo
command, confusing subsequent syntax. To avoid this, use an editor with syntax highlighting, like
vim
with the command :syntax on
.
Missing or Unexpected Tokens
Another common mistake is forgetting to complete a compound command, such as if
or while
.
For example, removing the semicolon after the test in the if
command:
#!/bin/bash
# trouble: script to demonstrate common errors
number=1
if [ $number = 1 ] then
echo "Number is equal to 1."
else
echo "Number is not equal to 1."
fi
Running the script results in:
[me@linuxbox ~]$ trouble
/home/me/bin/trouble: line 9: syntax error near unexpected token `else'
/home/me/bin/trouble: line 9: `else'
The error occurs later than the actual problem. The if
command's test list includes the word
then
due to the missing semicolon, causing syntax errors.
Unanticipated Expansions
Errors can occur intermittently due to the results of an expansion. For example, changing number
to an
empty variable:
#!/bin/bash
# trouble: script to demonstrate common errors
number=
if [ $number = 1 ]; then
echo "Number is equal to 1."
else
echo "Number is not equal to 1."
fi
Running the script results in:
[me@linuxbox ~]$ trouble
/home/me/bin/trouble: line 7: [: =: unary operator expected
Number is not equal to 1.
The test command fails due to the empty number
variable, resulting in [ = 1 ]
, which is
invalid. This can be corrected by adding quotes around the first argument in the test command:
[ "$number" = 1 ]
Then, the expansion results in [ "" = 1 ]
, which is valid.
Logical Errors
Logical errors do not prevent a script from running but cause it to produce incorrect results. Common types include:
- Incorrect conditional expressions: Misconfigured
if/then/else
logic. - “Off by one” errors: Loop counters starting from incorrect values.
- Unanticipated situations: Unexpected data or expansions causing failures.
Defensive Programming
Verify assumptions by carefully evaluating the exit status of programs and commands. For example, consider this script:
cd $dir_name
rm *
If dir_name
does not exist, the script will delete files in the current working directory. Improve it by
quoting dir_name
and checking if cd
is successful:
cd "$dir_name" && rm *
Further, check that dir_name
contains an existing directory:
[[ -d "$dir_name" ]] && cd "$dir_name" && rm *
Terminate the script if the directory does not exist:
# Delete files in directory $dir_name
if [[ ! -d "$dir_name" ]]; then
echo "No such directory: '$dir_name'" >&2
exit 1
fi
if ! cd "$dir_name"; then
echo "Cannot cd to '$dir_name'" >&2
exit 1
fi
if ! rm *; then
echo "File deletion failed. Check results" >&2
exit 1
fi
Watch Out for Filenames
Unix allows nearly any character in filenames, which can cause problems. For example, a filename starting with
-
can be interpreted as an option. Prevent this by using ./*
:
rm ./*
Portable Filenames
To ensure portability, limit filenames to the POSIX Portable Filename Character Set: uppercase and lowercase letters, numerals, period, hyphen, and underscore.
Verifying Input
A program must handle any input it receives. Use specific tests to verify valid input. For example, to verify a menu selection:
[[ $REPLY =~ ^[0-3]$ ]]
This returns a zero exit status only if REPLY
is a numeral between 0 and 3.
Design is a Function of Time
Design effort varies with the time available. Quick scripts for single use may need minimal checks, while production scripts require thorough development and testing.
Testing
Testing is crucial in software development. Use stubs to verify program flow and modify scripts to make tests safe. For example:
if [[ -d $dir_name ]]; then
if cd $dir_name; then
echo rm * # TESTING
else
echo "cannot cd to '$dir_name'" >&2
exit 1
fi
else
echo "no such directory: '$dir_name'" >&2
exit 1
fi
exit # TESTING
Test Cases
Develop good test cases to reflect edge and corner cases. For example, test the dir_name
script with:
dir_name
contains an existing directory.dir_name
contains a nonexistent directory.dir_name
is empty.
Debugging
Debugging involves finding out what a script is actually doing. Isolate problem areas by commenting out sections of code:
if [[ -d $dir_name ]]; then
if cd $dir_name; then
rm *
else
echo "cannot cd to '$dir_name'" >&2
exit 1
fi
# else
# echo "no such directory: '$dir_name'" >&2
# exit 1
fi
Tracing
Tracing shows the program's flow. Add informative messages or use the -x
option:
#!/bin/bash -x
# trouble: script to demonstrate common errors
number=1
if [ $number = 1 ]; then
echo "Number is equal to 1."
else
echo "Number is not equal to 1."
fi
Enable tracing for selected parts with set -x
and set +x
:
#!/bin/bash
# trouble: script to demonstrate common errors
number=1
set -x # Turn on tracing
if [ $number = 1 ]; then
echo "Number is equal to 1."
else
echo "Number is not equal to 1."
fi
set +x # Turn off tracing
Examining Values During Execution
Display variable contents during execution with echo
statements:
#!/bin/bash
# trouble: script to demonstrate common errors
number=1
echo "number=$number" # DEBUG
set -x # Turn on tracing
if [ $number = 1 ]; then
echo "Number is equal to 1."
else
echo "Number is not equal to 1."
fi
set +x # Turn off tracing
Summing Up
This chapter covered common script problems and debugging techniques. Debugging is a skill developed through experience, involving constant testing and effective use of tracing to find and fix bugs.