Debugging in Shell Script

What is Shell?

The UNIX shell program interprets user commands which are either directly entered by the user, or which can be read from a file called the shell script or shell program. Shell scripts are interpreted, not compiled. The shell reads commands from the script line per line and searches for those commands on the system.

The below command is used to check known shells in a UNIX system.

root@localhost$ cat /etc/shells 
# List of acceptable shells for chpass(1).
# Ftpd will not allow users to connect who are not using
# one of these shells.

/bin/bash
/bin/csh
/bin/dash
/bin/ksh
/bin/sh
/bin/tcsh
/bin/zsh

To change the shell, just write down the shell name; since a shell is an executable file (program), the current shell activates it and it gets executed. A new prompt is usually shown because each shell has its typical appearance.

Well, not knowing debugging the script will make your life miserable; you will be scratching your brain till your last nerve & still the issue will not be resolved. So, I would recommend to you guys that you must know about debugging because this will come in handy, believe me.

Debugging the script

Why debugging is important? Well, if things don’t go according to plan then we need to know where our script fails, right? So, this is where debugging comes into play.

The most basic step while debugging the script is, “echo”. You can “echo” the command on which you are using the variables so that you can check in the output section whether it is taking the right values or not.

#!/bin/bash

var="opstree"
echo "Hello, $var"

OUTPUT:

root@localhost$ ./script.sh 
Hello, opstree

Similarly, you can use this format for commands also.

 For extensive debugging, we use “set” which is Bash’s built-in.

Set -x

The most common one is the -x option, it will run the script in debug mode. Set -x shows the command as well as their output on the terminal so that you would know for which command, what the output is.

There are two ways to use the -x option, 

Example1: bash -x script.sh   ( while running the script )

Example2: #!/bin/bash -x        ( adding -x option in shebang )

Similarly, if we want to debug a particular part of the script on which you have doubts then we use set bash built-in.

#!/bin/bash

set -x       #Enabling the Debugging
echo "foo"
set +x       #Disabling the Debugging

echo "bar"

OUTPUT:

root@localhost$ ./script.sh 
+ echo foo
foo
+ set +x
bar

And -o xtrace is another way to write -x.

set -u

Sometimes you have not provided the variable, yet you have used it somewhere in the script. So, by default bash will ignore the variable that does not exist. So here, Script should report an error instead of continuing the execution silently.

#!/bin/bash
echo $foo
echo "bar"

OUTPUT:

root@localhost$ ./script.sh 

bar

In the above O/P, instead of throwing any error, our script has ignored the variable that is not defined and continues executing the remaining part of the script.

As a DevOps engineer, I don’t want my script to continue running even after some errors, so here we use,

Using, set -u

#!/bin/bash
set -u
echo $foo
echo "bar"

OUTPUT:

root@localhost$ ./script.sh 
./script.sh: line 3: foo: unbound variable

Also, -o nounset is another way to write -u. They are equivalent.

set -e

Well, bash will throw an error on any wrong command provided on the script, yet it will continue to execute the remaining part of the script. However, we don’t want bash to accumulate the errors instead, it should stop executing the script on the first error, right away.

For Example:

#!/bin/bash
foo
echo "bar"

OUTPUT:

root@localhost$ ./script.sh 
./script.sh: line 2: foo: command not found
bar

with set -e

#!/bin/bash
set -e
foo
echo "bar"

OUTPUT:

root@localhost$ ./script.sh 
./script.sh: line 3: foo: command not found

Set -e determines whether the script is a pass or fail, based on the return value. However, some commands may have a non-zero return value and that doesn’t indicate the running fails or you want the script to continue running even if the command fails, so for that, you can turn off “set -e” temporarily and enable “set -e” after the command ends.

#!/bin/bash
set +e
command1
command2
set -e

set +e means to turn off the -e option, and set -e means to turn on set -e again.

set-o pipefail

Set -e doesn’t apply to pipe commands because as we know “set -e” is determined by the return value & the pipe command will return the value of the last command, even if the first command fails.

Example:

#!/bin/bash
set -e
foo | echo "a"
echo "bar"

OUTPUT:

vikas.b4_ote$ ./script.sh 
a
./script.sh: line 3: foo: command not found
bar


So to overcome this issue, we can use “set -o pipefail”. As one of the subcommands fails, the script will terminate execution.

#!/bin/bash
set -eo pipefail
foo | echo "a"
echo "bar"

OUTPUT:

root@localhost$ ./script.sh 
a
./script.sh: line 3: foo: command not found

Displaying more bash options

-o option is used with a set to display all the options, you can use whatever options that you want to make your script more robust in nature.

root@localhost$ set -o
allexport      	off
braceexpand    	on
emacs          	on
errexit        	off
errtrace       	off
functrace      	off
hashall        	on
histexpand     	on
history        	on
ignoreeof      	off
interactive-comments	on
keyword        	off
monitor        	on
noclobber      	off
noexec         	off
noglob         	off
nolog          	off
notify         	off
nounset        	off
onecmd         	off
physical       	off
pipefail       	off
posix          	off
privileged     	off
verbose        	off
vi             	off
xtrace         	off

Conclusion

If you are reading the conclusion section, then I hope you guys have learned the debugging methods, which I have mentioned above. As we discussed, how we can stop the execution of our script whenever an error occurs. There are also multiple other methods that we can use but for now, we covered the widely used & general use cases, to see all the other methods we can use “set -o”.

I hope this blog has helped you with the debugging methods, through which we can make our script more robust in nature. Follow the steps mentioned above and you are good to go.

If you guys have any doubts or suggestions feel free to ask in the comment section below.

Blog Pundit: Bhupender rawat and Abhishek Dubey

Opstree is an End to End DevOps solution provider

Connect Us

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s