Americas

  • United States
sandra_henrystocker
Unix Dweeb

Troubleshooting your bash scripts

How-To
May 31, 20215 mins
Linux

audit binary compliance magnifying glass investigate
Credit: stevanovicigo / Getty

If you run into problems building, testing or running complex bash scripts, don’t lose heart. There are many ways you can help ensure that your scripts will work flawlessly. In this post, we’ll examine some ways you can lessen the likelihood of errors and how to go about doing some simple but very effective troubleshooting.

Through a combination of robust logic that tests for possible problems and some troubleshooting to help detect errors, your scripts are likely to be ready for showtime very quickly.

Building the outer edges first

One way to avoid syntactical errors in scripts is to start your for and while loops, case statements and if/then commands using the outer logic first. If you start your script logic using a syntactical “skeleton”, you won’t forget to end it properly.

for i in list    while [condition]   case "$1" in     if [ condition ]; then
do               do                    str1) cmd;;      cmds
  cmds             cmds                *) cmd;;       else
done             done                esac               cmds
                                                      fi

Go back and fill in the details after you put the basic command framework in place.

Verifying script arguments

To help ensure that a script won’t run into runtime problems, make sure it checks that the correct number of arguments are being provided and that any files provided as arguments actually exist. Here are some examples of implementing these checks.

Check the # of arguments

if [ $# -lt 2 ]; then
    echo "Usage: $0 lines filename"
    exit
else
    numlines=$1
    filename=$2
fi

Notice that a usage statement is generated if the arguments don’t match what is expected, and the script is then exited.

Check the type of an argument

if [[ $numlines != [0-9]* ]]; then
    echo "Error: $numlines is not numeric"
    exit
fi

Check that a file exists

if [ ! -f $filename]; then
    echo "Error: File $filename not found"
    exit
fi

Turning on bash debugging

Another way to test scripts it to turn on debugging. This will help you verify that they’re working properly or pinpoint any lines in the script that might be causing problems. You can turn on what is often called “debug mode” by using -x as an argument to bash. It helps in debugging by displaying each line in a script as it is being executed. This allows you to see which commands are working as expected and which are not.

You can use -x in one of two ways. You can start your script like this:

$ bash -x myscript

or you can insert the -x into the “shebang line” at the top of the script:

#!/bin/bash -x

Putting the -x option in the shebang line means you don’t have to type “bash -x scriptname”. Just remember to remove the “-x” once your debugging is complete.

You can also elect to turn debugging on for only a portion of a script. To do this, add “set -x” before the section of the script you want debug and “set +x” to turn debugging off after that section.

set -x
while [ $i -le 2 ]
do
  echo Number: $i
  ((i++))
done
set +x

The -v bash option works similarly to -x, but it displays each line in a script as it being read. This means that you will see the entire script and then its output as it is run. You can also use both with -xv as you bash argument (i.e., bash -xv).

Here’s  an example of using the bash -x form of debugging for a script that is intended to display the top lines of a file:

$ bash -x showtop
++ date +%s
+ STARTTIME=1622138266
++ - 1622138266
showtop: line 4: -: command not found            
+ echo 'It takes  seconds to complete this task...'
It takes  seconds to complete this task...
+ '[' 0 -lt 2 ']'
+ echo 'Usage: showtop lines filename'
Usage: showtop lines filename
+ exit

The fourth line of theh below output indicates that there’s a problem. For some reason, the script is looking for a command named “-“. Soon after, we see a usage statement indicating something is wrong with the arguments and the line above suggests that none were provided. In this case, the first error came about because $ENDTIME has no value at that point in the script, making $ENDTIME equal to an empty string, thus the calculation on the second line ends up being just “- 1622138266” and is clearly invalid.

#!/bin/bash

STARTTIME=$(date +%s)
timing=`expr $ENDTIME - $STARTTIME`
echo "It takes $timing seconds to complete this task"

That timing calculation belongs near the bottom of the script following collecting the ending time value using the date command as is in this corrected version:

#!/bin/bash

STARTTIME=$(date +%s)

if [ $# -lt 2 ]; then
    echo "Usage: $0 lines filename"
    exit
else
    numlines=$1
    filename=$2
fi

if [[ $numlines != [0-9]* ]]; then
    echo "Error: $numlines is not numeric"
    exit
fi

if [ ! -f $filename ]; then
    echo "Error: File $filename not found"
    exit
else
    echo top of file
    head -$numlines $filename
fi

ENDTIME=$(date +%s)
timing=`expr $ENDTIME - $STARTTIME`
echo "It took $timing seconds to complete this task"

Below is an example of running the repaired script in debug mode. Some of bottom lines (those shown in bold) are the intended script output. The other lines are the debugging output.

$ bash -x showtop 10 oldScript
++ date +%s
+ STARTTIME=1622143586
+ '[' 2 -lt 2 ']'
+ numlines=3
+ filename=oldScript
+ [[ 3 != [0-9]* ]]
+ '[' '!' -f oldScript ']'
+ echo top of file
top of file
+ head -3=10 oldScript
#!/bin/bash

STARTTIME=$(date +%s)
echo "It takes $($ENDTIME - $STARTTIME) seconds to complete this task..."

if [ $# -lt 2 ]; then
    echo "Usage: $0 lines filename"
    exit
else
    numlines=$1

++ date +%s
+ ENDTIME=1622143586
++ expr 1622143586 - 1622143586
+ timing=0
+ echo 'It took 0 seconds to complete this task...'
It took 0 seconds to complete this task...

Wrap-Up

Having scripts verify arguments and using the -x debugging option to pinpoint flaws can be extremely handy in getting scripts ready for use, especially for those which are far longer and more complicated than the example used in this post.

sandra_henrystocker
Unix Dweeb

Sandra Henry-Stocker has been administering Unix systems for more than 30 years. She describes herself as "USL" (Unix as a second language) but remembers enough English to write books and buy groceries. She lives in the mountains in Virginia where, when not working with or writing about Unix, she's chasing the bears away from her bird feeders.

The opinions expressed in this blog are those of Sandra Henry-Stocker and do not necessarily represent those of IDG Communications, Inc., its parent, subsidiary or affiliated companies.