How to Debug Shell Scripts

1 minute read

In order to find out where the problem is with my script, I often use echo for debugging, print line number with echo ${LINENO}, but it is not a optimium way for shell script debugging.

Shell debug/verbose mode

The best way to do shell script debugging is to utilize the debugging mode(-v -x), simply append -x, -v or -xv at the end of shebang line for whole script debugging, like this:

#!/bin/bash -x

# count words in string

foo="foo bar and 2000"

echo ${LINENO}
words=($foo)
echo ${#words[@]}

echo ${LINENO}
set -- $foo
echo $#

echo ${LINENO}
wc -w <<< "$foo"

The output of the above script will be like this:
xtrace mode

If using verbose mode, the output will be:
verbose xtrace mode

Another way to do this is to execute shell script preceding with bash -x

bash -x test.sh
bash -v test.sh
bash -xv test.sh

Debugging parts of the script

If we are sure only part of the script have issue, then use set -x at the start of the code need to be debugging, and unset xtrace mode with set +x at the end of the code.

#!/bin/bash

# count words in string

foo="foo bar and 2000"

echo ${LINENO}
words=($foo)
echo ${#words[@]}

set -x
echo ${LINENO}
set -- $foo
echo $#
set +x

echo ${LINENO}
wc -w <<< "$foo"

Show line number in debug mode

Shell debug mode is useful for trouble shooting, it would be more useful if adding source file, function and line number to the output. Do this with:

export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'

This is the example output after add source file, function and line number:

+(test.sh:5): main(): foo='foo bar and 2000'
+(test.sh:7): main(): echo 7
7
+(test.sh:8): main(): words=($foo)
+(test.sh:9): main(): echo 4
4
+(test.sh:11): main(): echo 11
11
+(test.sh:12): main(): set -- foo bar and 2000
+(test.sh:13): main(): echo 4
4
+(test.sh:15): main(): echo 15
15
+(test.sh:16): main(): wc -w
4

References