Learn Bash Debugging Techniques the Hard Way

In this article I’m going to give you a hands-on introduction to standard bash debugging techniques.

In addition, you’ll learn some techniques to make your bash scripts more robust to failure.

This article uses the hard way method, which emphasises hands-on-keyboard work to embed the learning. You’re going to have to think and type to learn.

Syntax Checking Options

Start by creating this simple script:

$ mkdir -p lbthw_debugging
$ cd lbthw_debugging
$ cat > debug_script.sh << 'END'
#!/bin/bash
A=some value
echo "${A}
echo "${B}"
END
$ chmod +x debug_script.sh

Now run it with the -n flag like this:

$ bash -n debug_script.sh -n

This flag only parses the script, rather than actually running it. It’s useful for detecting basic syntax errors.

You’ll see it’s broken. Fix it. Then run it again.

If you’re not sure how to fix, contact me.

Verbose and Trace Flags

Now run with -v to see the verbose output.

$ bash -v debug_script.sh

and then run with -x to trace the output:

$ bash -x debug_script.sh

What do you notice about the output of the commands? Read them carefully.

Do you see the problem?

Using these flags together can help debug scripts where there is an elementary error, or even just working out what’s going on when a script runs. I used -x only yesterday to figure out why a systemctl service wasn’t running or logging.

 


Material here based on the ‘advanced’ section of my book
Learn Bash the Hard Way.
Free preview available here.

hero


 

Managing Variables

Variables are a core part of most serious bash scripts (and even one-liners!), so managing them is another important way to reduce the possibility of your script breaking.

Change your script to add the ‘set’ line immediately after the first line and see what happens:

#!/bin/bash
set -o nounset
A="some value"
echo "${A}"
echo "${B}"

Now research what the nounset option does. Which set flag does this correspond to?

Now, without running it, try and figure out what this script will do. Will it run?

#!/bin/bash
set -o nounset
A="some value"
B=
echo "${A}"
echo "${B}"

I always set nounset on my scripts as a habit. It can catch many problems before they become serious.

Tracing Variables

If you are working with a particularly complex script, then you can get to the point where you are unsure what happened to a variable.

Try running this script and see what happens:

#!/bin/bash 
set -o nounset 
declare A="some value" 
function a { 
  echo "${BASH_SOURCE}>A A=${A} LINENO:${1}" 
} 
trap "a $LINENO" DEBUG 
B=value 
echo "${A}" 
A="another value" 
echo "${A}" 
echo "${B}"

There’s a problem with this code. The output is slightly wrong. Can you work out what is going on? If so, try and fix it.

You may need to refer to the bash man page, and make sure you understand quoting in bash properly.

It’s quite a tricky one to fix ‘properly’, so if you can’t fix it, or work out what’s wrong with it, then ask me directly and I will help.

Profiling Bash Scripts

Returning to the xtrace (or set -x flag), we can exploit its use of a PS variable to implement the profiling of a script:

#!/bin/bash
set -o nounset
set -o xtrace
declare A="some value"
PS4='$(date "+%s%N => ")'
B=
echo "${A}"
A="another value"
echo "${A}"
echo "${B}"
ls
pwd
curl -q bbc.co.uk

From this you should be able to tell what PS4 does. Have a play with it, and read up and experiment with the other PS variables to get familiar with what they do.

NOTE: If you are on a Mac, then you might only get
second-level granularity on the date!

 

Linting with Shellcheck

Finally, here is a very useful tip for understanding bash more deeply and improving any bash scripts you come across.

Shellcheck is a website and a package available on most platforms that gives you advice to help fix and improve your shell scripts. Very often, its advice has prompted me to research more deeply and understand bash better.

Here is some example output from a script I found on my laptop:

$ shellcheck shrinkpdf.sh
In shrinkpdf.sh line 44:
          -dColorImageResolution=$3             \
                                 ^-- SC2086: Double quote to prevent globbing and word splitting.
In shrinkpdf.sh line 46:
          -dGrayImageResolution=$3              \
                                ^-- SC2086: Double quote to prevent globbing and word splitting.
In shrinkpdf.sh line 48:
          -dMonoImageResolution=$3              \
                                ^-- SC2086: Double quote to prevent globbing and word splitting.
In shrinkpdf.sh line 57:
        if [ ! -f "$1" -o ! -f "$2" ]; then
                      ^-- SC2166: Prefer [ p ] || [ q ] as [ p -o q ] is not well defined.
In shrinkpdf.sh line 60:
        ISIZE="$(echo $(wc -c "$1") | cut -f1 -d\ )"
                      ^-- SC2046: Quote this to prevent word splitting.
                      ^-- SC2005: Useless echo? Instead of 'echo $(cmd)', just use 'cmd'.
In shrinkpdf.sh line 61:
        OSIZE="$(echo $(wc -c "$2") | cut -f1 -d\ )"
                      ^-- SC2046: Quote this to prevent word splitting.
                      ^-- SC2005: Useless echo? Instead of 'echo $(cmd)', just use 'cmd'.

The most common reminders are regarding potential quoting issues, but you can see other useful tips in the above output, such as preferred arguments to the test construct, and advice on “useless” echos.

 

Exercise

1) Find a large bash script on a social coding site such as GitHub, and run shellcheck over it. Contribute back any improvements you find.


 

3 thoughts on “Learn Bash Debugging Techniques the Hard Way

  1. Seriously, where has shellcheck been all my life?!

    And thank you for also explaining `bash -n`, `bash -v`, & `bash -x`.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.