How To Debug a Bash Shell Script Under Linux or UNIX

From my mailbag:
    I wrote a small hello world script. How can I Debug a bash shell scripts running on a Linux or Unix like systems?
It is the most common question asked by new sysadmins or Linux/UNIX user. Shell scripting debugging can be a tedious job (read as not easy). There are various ways to debug a shell script.

You need to pass the -x or -v argument to bash shell to walk through each line in the script.
How to debug a bash shell script on Linux or Unix
Let us see how to debug a bash script running on Linux and Unix using various methods.

ADVERTISEMENTS

-x option to debug a bash shell script

Run a shell script with -x option.
$ bash -x script-name
$ bash -x domains.sh

Use of set builtin command

Bash shell offers debugging options which can be turn on or off using the set command:

  • set -x : Display commands and their arguments as they are executed.
  • set -v : Display shell input lines as they are read.

You can use above two command in shell script itself:

#!/bin/bash
clear
 
# turn on debug mode
set -x
for f in *
do
   file $f
done
# turn OFF debug mode
set +x
ls
# more commands

You can replace the standard Shebang line:
#!/bin/bash
with the following (for debugging) code:
#!/bin/bash -xv

Use of intelligent DEBUG function

First, add a special variable called _DEBUG. Set _DEBUG to ‘on’ when you need to debug a script:
_DEBUG="on"

Put the following function at the beginning of the script:

function DEBUG()
{
 [ "$_DEBUG" == "on" ] &&  $@
}

Now wherever you need debugging simply use the DEBUG function as follows:
DEBUG echo "File is $filename"
OR
DEBUG set -x
Cmd1
Cmd2
DEBUG set +x

When done with debugging (and before moving your script to production) set _DEBUG to ‘off’. No need to delete debug lines.
_DEBUG="off" # set to anything but not to 'on'

Sample script:

#!/bin/bash
_DEBUG="on"
function DEBUG()
{
 [ "$_DEBUG" == "on" ] &&  $@
}
 
DEBUG echo 'Reading files'
for i in *
do
  grep 'something' $i > /dev/null
  [ $? -eq 0 ] && echo "Found in $i file"
done
DEBUG set -x
a=2
b=3
c=$(( $a + $b ))
DEBUG set +x
echo "$a + $b = $c"

Save and close the file. Run the script as follows:
$ ./script.sh
Output:

Reading files
Found in xyz.txt file
+ a=2
+ b=3
+ c=5
+ DEBUG set +x
+ '[' on == on ']'
+ set +x
2 + 3 = 5

Now set DEBUG to off (you need to edit the file):
_DEBUG="off"
Run script:
$ ./script.sh
Output:

Found in xyz.txt file
2 + 3 = 5

Above is a simple but quite effective technique. You can also try to use DEBUG as an alias instead of function.

Debugging Common Bash Shell Scripting Errors

Bash or sh or ksh gives various error messages on screen and in many case the error message may not provide detailed information.

Skipping to apply execute permission on the file

When you write your first hello world bash shell script, you might end up getting an error that read as follows:
bash: ./hello.sh: Permission denied
Set permission using chmod command:
$ chmod +x hello.sh
$ ./hello.sh
$ bash hello.sh

End of file unexpected Error

If you are getting an End of file unexpected error message, open your script file and and make sure it has both opening and closing quotes. In this example, the echo statement has an opening quote but no closing quote:

#!/bin/bash
...
....
echo 'Error: File not found
                                        ^^^^^^^
                                        missing quote

Also make sure you check for missing parentheses and braces ({}):

#!/bin/bash
.....
[ ! -d $DIRNAME ] && { echo "Error: Chroot dir not found"; exit 1;
                                                                    ^^^^^^^^^^^^^
                                                                    missing brace }
...

Missing Keywords Such As fi, esac, ;;, etc.

If you missed ending keyword such as fi or ;; you will get an error such as as “xxx unexpected”. So make sure all nested if and case statements ends with proper keywords. See bash man page for syntax requirements. In this example, fi is missing:

#!/bin/bash
echo "Starting..."
....
if [ $1 -eq 10 ]
then
   if [ $2 -eq 100 ]
   then
      echo "Do something"
fi
 
for f in $files
do
  echo $f
done
 
# note fi is missing

Moving or editing shell script on Windows or Unix boxes

Do not create the script on Linux/Unix and move to Windows. Another problem is editing the bash shell script on Windows 10 and move/upload to Unix server. It will result in an error like command not found due to the carriage return (DOS CR-LF). You can convert DOS newlines CR-LF to Unix/Linux format using the following syntax:
dos2unix my-script.sh

Tip 1 – Send Debug Message To stderr

Standard error is the default error output device, which is used to write all system error messages. So it is a good idea to send messages to the default error device:

# Write error to stdout
echo "Error: $1 file not found"
#
# Write error to stderr (note 1>&2 at the end of echo command)
#
echo "Error: $1 file not found" 1>&2

Tip 2 – Turn On Syntax Highlighting when using vim text editor

Most modern text editors allows you to set syntax highlighting option. This is useful to detect syntax and prevent common errors such as opening or closing quote. You can see bash script in different colors. This feature eases writing in a shell script structures and syntax errors are visually distinct. Highlighting does not affect the meaning of the text itself; it’s made only for you. In this example, vim syntax highlighting is used for my bash script:

How To Debug a Bash Shell Script Under Linux or UNIX Using Vim Syntax Highlighting Feature

Fig.01: Bash shell script syntax highlighting using vim text editor

Tip 3 – Use shellcheck to lint script

ShellCheck is a static analysis tool for shell scripts. One can use it to finds bugs in your shell scripts. It is written in Haskell. You can find warnings and suggestions for bash/sh shell scripts with this tool. Let us see how to install and use ShellCheck on a Linux or Unix-like system to enhance your shell scripts, avoid errors and productivity.

See also:

🐧 Get the latest tutorials on SysAdmin, Linux/Unix, Open Source & DevOps topics via:
CategoryList of Unix and Linux commands
File Managementcat
FirewallAlpine Awall CentOS 8 OpenSUSE RHEL 8 Ubuntu 16.04 Ubuntu 18.04 Ubuntu 20.04
Network Utilitiesdig host ip nmap
OpenVPNCentOS 7 CentOS 8 Debian 10 Debian 8/9 Ubuntu 18.04 Ubuntu 20.04
Package Managerapk apt
Processes Managementbg chroot cron disown fg jobs killall kill pidof pstree pwdx time
Searchinggrep whereis which
User Informationgroups id lastcomm last lid/libuser-lid logname members users whoami who w
WireGuard VPNAlpine CentOS 8 Debian 10 Firewall Ubuntu 20.04

ADVERTISEMENTS
24 comments… add one
  • _ranger_ Jan 31, 2007 @ 15:30

    For running under “set -x” or “set +x”, why not just run it as:

    bash -x ./script.sh

    Also, instead of having _DEBUG in your script, just export it from your shell:

    $ export _DEBUG=on

    Now, you have to make absolutely no changes to enable or disable all debugging.

  • 🐧 nixCraft Feb 1, 2007 @ 22:00

    _ranger_,

    Good idea however you may have to modify script little to check if _DEBUG is defined or not.

    Appreciate your post.

  • sartan Feb 19, 2008 @ 0:59

    I liked this debug method and implemented it in a simple script I wrote 🙂 It really does make for much cleaner code than what I have been doing before. Thanks mate

  • thievm May 8, 2008 @ 11:33

    thanks,it is good idea

  • mosjin May 20, 2008 @ 7:37

    It’s a good idea. Thanks!

  • yura Dec 27, 2008 @ 20:52
  • dekkard Jan 11, 2010 @ 6:37

    very nice, thanx

  • psc Jan 27, 2010 @ 22:01

    [ “$_DEBUG” == “on” ] && $@ || :

    Why are you using last || operator?

    • 🐧 nixCraft Jan 28, 2010 @ 4:36

      Yes, it is not required.

      • Philippe Petrinko May 19, 2010 @ 17:06

        Hi Vivek,
        if this is not required, would you consider cleaning code – mainly because that may confuse novice programmers and may induce bugs … Thanks in advance! Nice topic, one more!

        • 🐧 nixCraft May 20, 2010 @ 20:17

          Philippe,

          Thanks for your feedback. The post has been updated.

    • geek May 16, 2013 @ 18:07

      || : will make sure the return code is true, which allows the script to run under -e mode without it breaking due to problems with debug commands. Using -e when debugging may be very helpful.

      On a related note: You can set variables on a by-call basis like this:
      _DEBUG=”on” command. No export required, thus no later unexport required. All nice and local.

      • Harvey Jun 13, 2013 @ 1:27

        The || should remain so it will work with “set -e”. To make it clearer, perhaps:

        [ “$_DEBUG” == “on” ] && $@ || return 0

  • SS Senthil Kumar Mar 22, 2011 @ 17:03

    I am going to move from HP-Unix to Redhat Linux server. I have many bash shell programs in HP-Unix. Will they need change when i move to Linux (RHEL)? Kindly advice.

  • achyut Jul 6, 2011 @ 12:27

    Excellent Topic

  • igor Jan 16, 2013 @ 13:03

    Very helpful. I have bookmarked this page, as it will help me port a Windows batch file into a Linux script.

  • sai ram Sep 10, 2013 @ 12:15

    why are we using this instruction >> “$_DEBUG” == “on” ] && $@

  • Dave Lane Oct 21, 2013 @ 3:37

    Very useful – thank you!

    You can change the function to handle multiline statements with the following:

    function DEBUG {
        if [ "$_DEBUG" == "on" ] ; then
            $@
        else
            : $@
        fi
    }
  • Dave Lane Oct 21, 2013 @ 4:02

    Or, better:-

    function DEBUG {
        if [ "$_DEBUG" == "on" ] ; then
            "$@"
        else
            : "$@"
        fi
    }
    

    This handles statements like:-

    DEBUG printf "%12d     %sn" 
                123 "hello"
  • Laurent_L Mar 30, 2014 @ 18:23

    Hello,

    First, many thanks for this very good HowTo.

    About the function DEBUG:
    In the form proposed

    function DEBUG()
    {
     [ "$_DEBUG" == "on" ] &&  $@
    }

    it’s OK for an echo command, but if I try

    a=1
    DEBUG a=2

    I obtain an error message.
    So, I’m using this form :

    function DEBUG()
    {
     [ "$_DEBUG" == "on" ] && eval $@
    }

    which is OK for me.
    More precisely, my debug function is

    debug_cmd ()
    {
        if [ ! -z "${DEBUG}" ]
        then
            msg_head=$(caller 0)
            echo "[ ${msg_head} ] $@" >&2
            read -p "Run it [N/y] ? "
            if [ "$REPLY" = "y" ] || [ "$REPLY" = "Y" ]
            then
                eval $@
            fi
        fi
    }

    This one allows me to view the command before choosing to run it or to ignore it.
    It’s OK for a command like ‘a=2’, but fails with a command ‘echo “pouet (a=$a)”.
    Not really a problem if we use debug_cmd only for commands like variable affectation, or function calls, not for simple echo commands.

    Regards,

    Laurent.

  • Damanjeet Sep 4, 2015 @ 4:05

    Thanks Excellent tutorials

  • ashick Sep 8, 2015 @ 7:02

    good stuff #thank you

  • Gopal Oct 20, 2015 @ 3:21

    Thanks ..
    It’s really helpful

  • Shaikh Aafaque Mar 15, 2016 @ 8:30

    I normally work on the production environment with no access to UAT/Dev servers.

    If I use the -x option in production, will my script actually run while debugging or it will only debug.

    Actually, I have a script, which does a lot of data changing. So I just wanted to debug without actually running the script.

Leave a Reply

Your email address will not be published. Required fields are marked *

Use HTML <pre>...</pre>, <code>...</code> and <kbd>...</kbd> for code samples.