Even shell scripts may get very complex, so it is helpful to know how to debug them.

Lets explain it on a small example:

#/bin/bash

echo lets go

# some comment
DIR=/boot
/bin/ls -l $DIR | /bin/grep initrd  | wc -l

echo done

Executing it you’ll get an output like this:

usr@srv /tmp % bash test.sh
lets go
112
done

To debug the execution of scripts the bash provides a debugging mode. There is one option -x to trace the execution

usr@srv /tmp % bash -x test.sh
+ echo lets go
lets go
+ DIR=/boot
+ wc -l
+ /bin/grep initrd
+ /bin/ls -l /boot
112
+ echo done
done

So you see, every line that is executed at the runtime will be printed with a leading + , comments are ignored. There is another option -v to enable verbose mode. In this mode each line that is read by the bash will be printed before it is executed:

usr@srv /tmp % bash -v test.sh
#/bin/bash

echo lets go
lets go

# some comment
DIR=/boot
/bin/ls -l $DIR | /bin/grep initrd  | wc -l
112

echo done
done

Of course you can combine both modes, so the script is sequentially printed and the commands are traced:

usr@srv /tmp % bash -vx test.sh
#/bin/bash

echo lets go
+ echo lets go
lets go

# some comment
DIR=/boot
+ DIR=/boot
/bin/ls -l $DIR | /bin/grep initrd  | wc -l
+ /bin/ls -l /boot
+ wc -l
+ /bin/grep initrd
112

echo done
+ echo done
done

These modes will help you to find some errors. To modify the output of the tracing mode you may configure the PS4 :

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

This will also print the file name of the executing script, the line number of the current command that is executed and the respective function name:

usr@srv /tmp % export 'PS4=+${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]}: '
usr@srv /tmp % bash -x test.sh
+test.sh:3:: echo lets go
lets go
+test.sh:6:: DIR=/boot
+test.sh:7:: /bin/ls -l /boot
+test.sh:7:: /bin/grep initrd
+test.sh:7:: wc -l
112
+test.sh:9:: echo done
done

if You don’t want to trace a whole script you can enable/disable tracing from within a script:

# [...]
echo no tracing
set -x
echo trace me
set +x
echo no tracing
# [...]

This will result in something like:

usr@srv /tmp % bash test.sh
[...]
no tracing
+test.sh:14:: echo trace me
trace me
+test.sh:15:: set +x
no tracing
[...]

It is of course also possible to enable/disable verbose mode inside the script with set -v and set +v , respectively.


Martin Scharm

stuff. just for the records.

Do you like this page?
You can actively support me!

4 comments

anjan bacchu | Permalink |

thanks for the article.

Florian Heigl | Permalink |

Hi,

there’s two more things I love in debugging scripts:

set -n “parse it but don’t run it” (thats what I call as the first test of a new script) So, I very very often use this. The next one I only use rarely outside of debugging: set -e “exit on the first error” This can be interesting to catch errors that happen early in a script. Ideally, you handled all expected error scenarios and then set -e is catching on some more unexpected ones. Very naive people think it to be error handling if they put set -e in the start of a script, they are wrong ;) For debugging it’s definitely great. What other purposes? Very critical scripts that are better dead than doing anything unexpected AND also able to clean up their last run.

Give it a test next time :> And thanks for the wordpress Nagios check. Bye

Mayur Rokade | Permalink |

Thanks for nice tip. Btw I tweaked your PS4 value for color high lighting of green colored line numbers. Here is the PS4 I used.

export 'PS4=\[\e[1;32m\]\][Line: ${LINENO}]\[\e[0m\]\] :${FUNCNAME[0]}:'

For more terminal color info check: https://wiki.archlinux.org/index.php/Color_Bash_Prompt

Martin Scharm | Permalink |

Thanks, seems to be quite useful :)

Leave a comment

There are multiple options to leave a comment: