Basics of Debugging Linux Shell Scripts

There are two ways to specify debugging options.

One is to specify options when executing the <span>bash</span> command:

# -x prints commands and their arguments during execution
bash -x debug.sh

The other is to set it in the <span>bash</span> script using the built-in command <span>set</span>:

#!/bin/bash

set -x # Enable debugging
# Here is some code
set +x # Disable debugging

Generally, the following options are used to debug scripts.

Option Description
-x Debug mode: displays each command executed
-v Verbose mode: displays each line of the script
-n Syntax check: checks without executing
-u Strict mode: reports an error for undefined variables
-e Fail fast: exits immediately if a command fails
-o pipefail Pipe fail: exits if any command in a pipeline fails

1. Debugging Options

First, prepare a script called <span>debug.sh</span>:

#!/usr/bin/env bash
radius=2.5
result=$(awk -v r=$radius 'BEGIN {printf "%.3f", 3.14159 * r * r}')
echo "area: $result"

<span>-x: Debug mode: displays each command executed</span>

bash-5.3$ bash -v debug.sh
+ radius=2.5
++ awk -v r=2.5 'BEGIN {printf "%.3f", 3.14159 * r * r}'
+ result=19.635
+ echo 'area: 19.635'
area: 19.635

<span>-v: Verbose mode: displays each line of the script</span>

bash-5.3$ bash -v debug.sh
#!/usr/bin/env bash
radius=2.5
result=$(awk -v r=$radius 'BEGIN {printf "%.3f", 3.14159 * r * r}')
echo "area: $result"
area: 19.635

<span>-xv: Combination of debug mode and verbose mode</span>

#!/usr/bin/env bash
radius=2.5
+ radius=2.5
result=$(awk -v r=$radius 'BEGIN {printf "%.3f", 3.14159 * r * r}')
++ awk -v r=2.5 'BEGIN {printf "%.3f", 3.14159 * r * r}'
+ result=19.635
echo "area: $result"
+ echo 'area: 19.635'
area: 19.635

From the above, we can see:<span>-v</span>: displays each line of the script followed by <span>-x</span>: displays each command executed. We can also see the result of parameter expansion for <span>r=2.5</span>.

Let’s also debug the <span>glob</span> pattern:

bash-5.3$ set -xv
bash-5.3$ ls -l *
ls -l *
+ ls -l log1.txt log2.txt log3.txt
-rw-r--r--  1 user  staff  19 11  4 16:01 log1.txt
-rw-r--r--  1 user  staff   0 11  4 12:40 log2.txt
-rw-r--r--  1 user  staff   0 11  4 12:40 log3.txt
bash-5.3$ set +xv

<span>-n: Syntax check: checks without executing</span>

# No syntax issues
bash-5.3$ bash -n debug.sh
# No output

Now change <span>radius=2.5</span> to <span>radius = 2.5</span>, adding spaces around the equal sign.

#!/usr/bin/env bash
radius = 2.5
result=$(awk -v r=$radius 'BEGIN {printf "%.3f", 3.14159 * r * r}')
echo "area: $result"
area: 19.635

Execute again:

bash-5.3$ bash -n debug.sh
# what? No output.
# The author is confused again......

Here, the author indeed did not detect the <span>radius = 2.5</span> syntax error with spaces around the equal sign using <span>-n</span>. Is this a runtime error?

The actual execution will result in an error:

./debug.sh: line 2: radius: command not found
area: 0.000

Add an erroneous <span>if</span> statement:

#!/usr/bin/env bash

if [ "$1" = "2" ] ; then
    echo 'hello'

radius = 2.5
result=$(awk -v r=${radius} 'BEGIN {printf "%.3f", 3.14159 * r * r}')
echo "area: $result"

This time it was detected:

bash-5.3$ bash -n debug.sh
debug.sh: line 10: syntax error: unexpected end of file in "if" command on line 3

<span>-e: Fail fast: exits immediately if a command fails</span>

#!/usr/bin/env bash
# strict_e.sh
echo "Starting"
# This will cause the script to exit because the directory may not exist
ls /nonexistent_directory
echo "This line will not execute"

<span>Result:</span>

bash-5.3$ bash strict_e.sh
Starting
ls: /nonexistent_directory: No such file or directory
This line will not execute
bash-5.3$ bash -e strict_e.sh
Starting
ls: /nonexistent_directory: No such file or directory

<span>-u: Strict mode: reports an error for undefined variables</span>

#!/usr/bin/env bash
# strict_u.sh
# Using an undefined variable
undefined_variable=$NONEXISTENT_VAR
echo "This line will not execute"

<span>Result:</span>

bash-5.3$ bash strict_u.sh
This line will not execute
bash-5.3$ bash -u strict_u.sh
strict_u.sh: line 4: NONEXISTENT_VAR: unbound variable

<span>-o pipefail: Pipe fail: exits if any command in a pipeline fails</span>

  • <span>-e</span> has an exception, as it does not apply to pipeline commands.
#!/usr/bin/env bash
# pipefail.sh

unknown_cmd | echo a
echo hello

<span>Run it:</span>

bash-5.3$ bash pipefail.sh
pipefail.sh: line 4: unknown_cmd: command not found
a
hello
bash-5.3$ bash -e pipefail.sh
pipefail.sh: line 4: unknown_cmd: command not found
a
hello

From the execution results, we can see that <span>unknown_cmd | echo a</span> is successful, so the subsequent code continues to execute.

bash-5.3$ bash -o pipefail pipefail.sh
pipefail.sh: line 4: unknown_cmd: command not found
a
hello
bash-5.3$ bash -eo pipefail pipefail.sh
pipefail.sh: line 4: unknown_cmd: command not found
a

<span>-eo pipefail</span> must be specified together to exit on <span>pipe fail</span>, compensating for the exception of <span>-e</span>.

2. Summary

Generally, options are used together:

bash -euxo pipefail script.sh

Additionally, options can also be specified in the script file using <span>set</span>:

# Method 1
set -euxo pipefail

# Method 2
set -eux
set -o pipefail

Finally, as a good practice, it is recommended to specify <span>set -euo pipefail</span> in each script file.

#!/usr/bin/env bash

set -euo pipefail

# Following is functional code

Leave a Comment