171

Is there a way to debug a Bash script?

E.g., something that prints a sort of an execution log, like "calling line 1", "calling line 2", etc.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
corvus
  • 2,330
  • 2
  • 18
  • 16

12 Answers12

204
sh -x script [arg1 ...]
bash -x script [arg1 ...]

These give you a trace of what is being executed. (See also 'Clarification' near the bottom of the answer.)

Sometimes, you need to control the debugging within the script. In that case, as Cheeto reminded me, you can use:

set -x

This turns debugging on. You can then turn it off again with:

set +x

(You can find out the current tracing state by analyzing $-, the current flags, for x.)

Also, shells generally provide options '-n' for 'no execution' and '-v' for 'verbose' mode; you can use these in combination to see whether the shell thinks it could execute your script — occasionally useful if you have an unbalanced quote somewhere.


There is contention that the '-x' option in Bash is different from other shells (see the comments). The Bash Manual says:

  • -x

    Print a trace of simple commands, for commands, case commands, select commands, and arithmetic for commands and their arguments or associated word lists after they are expanded and before they are executed. The value of the PS4 variable is expanded and the resultant value is printed before the command and its expanded arguments.

That much does not seem to indicate different behaviour at all. I don't see any other relevant references to '-x' in the manual. It does not describe differences in the startup sequence.

Clarification: On systems such as a typical Linux box, where '/bin/sh' is a symlink to '/bin/bash' (or wherever the Bash executable is found), the two command lines achieve the equivalent effect of running the script with execution trace on. On other systems (for example, Solaris, and some more modern variants of Linux), /bin/sh is not Bash, and the two command lines would give (slightly) different results. Most notably, '/bin/sh' would be confused by constructs in Bash that it does not recognize at all. (On Solaris, /bin/sh is a Bourne shell; on modern Linux, it is sometimes Dash — a smaller, more strictly POSIX-only shell.) When invoked by name like this, the 'shebang' line ('#!/bin/bash' vs '#!/bin/sh') at the start of the file has no effect on how the contents are interpreted.

The Bash manual has a section on Bash POSIX mode which, contrary to a long-standing but erroneous version of this answer (see also the comments below), does describe in extensive detail the difference between 'Bash invoked as sh' and 'Bash invoked as bash'.

When debugging a (Bash) shell script, it will be sensible and sane — necessary even — to use the shell named in the shebang line with the -x option. Otherwise, you may (will?) get different behaviour when debugging from when running the script.

Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 1
    He did specify a `bash` script. And running a bash script with `sh -x` will cause it to behave completely different! Please update your answer. – lhunath Jun 04 '09 at 16:11
  • 1
    @lhunath: In what way does 'sh -x' (or 'bash -x') make a script behave completely differently? Obviously, it outputs trace information to stderr; that's a given (though not mentioned in my answer). But what else? I use 'bash' as 'sh' on Linux and MacOS X and haven't noticed a serious issue. – Jonathan Leffler Jun 04 '09 at 18:02
  • 6
    There are differences, in startup and under runtime. They're fully documented in the Bash distribution. – TheBonsai Jun 04 '09 at 19:14
  • 1
    Let me clarify: Running a `bash` script with `sh` causes the interpreter to disable (almost) all bash-features, leaving only the POSIX ones. Since OP said his script was a bash script, it should not be ran with sh. If it is, masses of errors will follow; unexpected behaviour; and depending on the script, really bad stuff could happen like files getting deleted and so on. – lhunath Jun 04 '09 at 20:43
  • 4
    Here's a link to the bash doc: http://www.gnu.org/software/bash/manual/bashref.html#Bash-Startup-Files 'If Bash is invoked with the name sh, it tries to mimic the startup behavior of historical versions of sh as closely as possible, while conforming to the posix standard as well' – thethinman Jan 15 '10 at 21:06
  • 7
    And use the PS4 prompt to give more useful information like: `export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'` – estani Sep 17 '12 at 10:15
  • 1
    "On systems such as a typical Linux box, where '/bin/sh' is a symlink to '/bin/bash'" Note: On ubuntu /bin/sh is dash, not bash. see https://wiki.ubuntu.com/DashAsBinSh – Tolli Mar 24 '14 at 17:51
  • 1
    @Tolli: so Ubuntu is not a typical Linux box, or what was typical in 2010 is no longer typical in 2014. Or both... – Jonathan Leffler Mar 24 '14 at 18:07
28

I've used the following methods to debug my script.

set -e makes the script stop immediately if any external program returns a non-zero exit status. This is useful if your script attempts to handle all error cases and where a failure to do so should be trapped.

set -x was mentioned above and is certainly the most useful of all the debugging methods.

set -n might also be useful if you want to check your script for syntax errors.

strace is also useful to see what's going on. Especially useful if you haven't written the script yourself.

  • 1
    Stracing a script (i.e. stracing a shell executing the script) is a strange shell debugging method (but may work for a limited set of issues). – TheBonsai Jun 04 '09 at 19:17
  • 1
    I admit it is strange and also very verbose, but if you limit the output of strace to a few syscalls, it becomes useful. –  Jun 04 '09 at 20:12
  • 1
    Note that `strace -f` is needed if you also want to find errors in the processes started by the script. (which makes it many times more verbose, but still useful if you limit it to the syscalls you're interested in). – Random832 Apr 06 '11 at 00:36
  • `set -e` is... [controvercial](http://mywiki.wooledge.org/BashFAQ/105). – Charles Duffy Apr 02 '17 at 13:37
13

Jonathan Leffler's answer is valid and useful.

But, I find that the "standard" script debugging methods are inefficient, unintuitive, and hard to use. For those used to sophisticated GUI debuggers that put everything at your fingertips and make the job a breeze for easy problems (and possible for hard problems), these solutions aren't very satisfactory.

I do use a combination of DDD and bashdb. The former executes the latter, and the latter executes your script. This provides a multi-window UI with the ability to step through code in context and view variables, stack, etc., without the constant mental effort to maintain context in your head or keep re-listing the source.

There is guidance on setting that up in DDD and BASHDB.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Stabledog
  • 139
  • 1
  • 2
  • Just discovered ddd thanks to your answer. In Ubuntu 12.04.3 (64bit), the apt-sources version doesn't work. I had to compile & install from source to start debugging my bash script. The instructions here - http://askubuntu.com/questions/156906/how-do-i-get-the-bash-debugger-to-work-with-ddd helped. – chronodekar Oct 18 '13 at 09:20
  • Yes, that's a problem. I solved it some time back with some scripting -- 'dddbash' installs/builds DDD, removes the old version if its wrong, installs bashdb, etc. (Answer has been edited with this info now) – Stabledog Feb 26 '14 at 15:14
11

I found the shellcheck utility and maybe some folks find it interesting.

A little example:

$ cat test.sh
ARRAY=("hello there" world)

for x in $ARRAY; do
  echo $x
done

$ shellcheck test.sh

In test.sh line 3:
for x in $ARRAY; do
         ^-- SC2128: Expanding an array without an index only gives the first element.

Fix the bug. First try...

$ cat test.sh
ARRAY=("hello there" world)

for x in ${ARRAY[@]}; do
  echo $x
done

$ shellcheck test.sh

In test.sh line 3:
for x in ${ARRAY[@]}; do
         ^-- SC2068: Double quote array expansions, otherwise they're like $* and break on spaces.

Let's try again...

$ cat test.sh
ARRAY=("hello there" world)

for x in "${ARRAY[@]}"; do
  echo $x
done

$ shellcheck test.sh

Found now!

It's just a small example.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Equation Solver
  • 545
  • 7
  • 18
10

You can also write "set -x" within the script.

Cheeto
  • 151
  • 3
4

Install Visual Studio Code, and then add the Bash debug extension and you are ready to debug in visual mode. See it here in action.

Enter image description here

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
yantaq
  • 3,968
  • 2
  • 33
  • 34
3

Use Eclipse with the plugins Shelled and BashEclipse.

For Shelled: Download the ZIP file and import it into Eclipse via menu HelpInstall new software: local archive. For BashEclipse: Copy the JAR files into the dropins directory of Eclipse

Follow the steps provided in BashEclipse files

Enter image description here

I wrote a tutorial with many screenshots at Bash: enabling Eclipse for Bash Programming | Plugin Shelled (shell editor)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • 3
    This is a borderline [link-only answer](//meta.stackexchange.com/q/8231) (also see [here](https://meta.stackexchange.com/q/8231/271271)). You should expand your answer to include as much information here as possible, at least the minimum needed to actually accomplish what you are suggesting, and use the links only for reference. Basically, posts on Stack Overflow (and all of Stack Exchange) must be self-contained. That means that enough information needs to be in your answer such that the reader doesn't *need to* go off-site for directions. Right now, that's not the case for this answer. – Makyen Aug 08 '18 at 16:42
  • this is the first answer that I have found after looking at many that actually shows that true debugging is possible. The standard answers "set +x" perfectly match a self contained answer but almost willfully ignore the true questions about true debugging. I applaud this answer – simbo1905 Apr 24 '19 at 17:25
  • The http://dietrichschroff.blogspot.com/ page is very slow to *fully* load (or times out). – Peter Mortensen Oct 25 '21 at 18:20
  • The first link, `https://sourceforge.net/projects/shelled/?source=directory` redirects to `https://sourceforge.net/projects/dvkit/?source=directory` ("DVKit") and superficially does not ***seem*** to have anything to do "Shelled". An explanation would be in order. – Peter Mortensen Oct 25 '21 at 18:25
  • How is this different from [the first attempt](https://stackoverflow.com/questions/951336/how-can-i-debug-a-bash-script/45087661#45087661)? – Peter Mortensen Oct 25 '21 at 18:26
2

I built a Bash debugger: Bash Debuging Bash. Just give it a try.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
abadjm
  • 156
  • 5
  • BDB is currently supported in English and Spanish languages. To change the language, edit the file /etc/default/bdb – abadjm Mar 01 '14 at 05:06
  • the screenshot looks interesting but I couldnt make it run "bdb.sh: line 32: bdbSTR[1]: unbound variable"; btw will it show the current values of all set variables each step we do on the code? – Aquarius Power Jun 26 '14 at 00:46
2
set +x = @ECHO OFF, set -x = @ECHO ON.

You can add the -xv option to the standard shebang as follows:

#!/bin/bash -xv

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


ltrace is another Linux utility similar to strace. However, ltrace lists all the library calls being called in an executable or a running process. Its name itself comes from library-call tracing.

For example:

ltrace ./executable <parameters>
ltrace -p <PID>

Source

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Premraj
  • 72,055
  • 26
  • 237
  • 180
1

I think you can try this Bash debugger: http://bashdb.sourceforge.net/.

Nan Xiao
  • 16,671
  • 18
  • 103
  • 164
  • There's good amount of detail on logging for shell scripts via global varaibles of shell. We can emulate the similar kind of logging in shell script: http://www.cubicrace.com/2016/03/efficient-logging-mechnism-in-shell.html – Piyush Chordia Mar 02 '16 at 13:49
1

There's a good amount of detail on logging for shell scripts via the global variables of the shell. We can emulate a similar kind of logging in a shell script: Log tracing mechanism for shell scripts

The post has details on introducing log levels, like INFO, DEBUG, and ERROR. Tracing details like script entry, script exit, function entry, function exit.

Sample log:

Enter image description here

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Piyush Chordia
  • 1,335
  • 1
  • 9
  • 7
1

Some trick to debug Bash scripts:

Using set -[nvx]

In addition to

set -x

and

set +x

for stopping dump.

I would like to speak about set -v which dump as smaller as less developed output.

bash <<<$'set -x\nfor i in {0..9};do\n\techo $i\n\tdone\nset +x' 2>&1 >/dev/null|wc -l
21

for arg in x v n nx nv nvx;do echo "- opts: $arg"
    bash 2> >(wc -l|sed s/^/stderr:/) > >(wc -l|sed s/^/stdout:/) <<eof
        set -$arg
        for i in {0..9};do
            echo $i
          done
        set +$arg
        echo Done.
eof
    sleep .02
  done
- opts: x
stdout:11
stderr:21
- opts: v
stdout:11
stderr:4
- opts: n
stdout:0
stderr:0
- opts: nx
stdout:0
stderr:0
- opts: nv
stdout:0
stderr:5
- opts: nvx
stdout:0
stderr:5

Dump variables or tracing on the fly

For testing some variables, I use sometime this:

bash <(sed '18ideclare >&2 -p var1 var2' myscript.sh) args

for adding:

declare >&2 -p var1 var2

at line 18 and running the resulting script (with args), without having to edit them.

Of course, this could be used for adding set [+-][nvx]:

bash <(sed '18s/$/\ndeclare -p v1 v2 >\&2/;22s/^/set -x\n/;26s/^/set +x\n/' myscript) args

It will add declare -p v1 v2 >&2 after line 18, set -x before line 22 and set +x before line 26.

A little sample:

bash <(sed '2,3s/$/\ndeclare -p LINENO i v2 >\&2/;5s/^/set -x\n/;7s/^/set +x\n/' <(
        seq -f 'echo $@, $((i=%g))' 1 8)) arg1 arg2
arg1 arg2, 1
arg1 arg2, 2
declare -i LINENO="3"
declare -- i="2"
/dev/fd/63: line 3: declare: v2: not found
arg1 arg2, 3
declare -i LINENO="5"
declare -- i="3"
/dev/fd/63: line 5: declare: v2: not found
arg1 arg2, 4
+ echo arg1 arg2, 5
arg1 arg2, 5
+ echo arg1 arg2, 6
arg1 arg2, 6
+ set +x
arg1 arg2, 7
arg1 arg2, 8

Note: Care about $LINENO. It will be affected by on-the-fly modifications!

(To see resulting script without executing, simply drop bash <( and ) arg1 arg2)

Step by step, execution time

Have a look at my answer about how to profile Bash scripts.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
F. Hauri - Give Up GitHub
  • 64,122
  • 17
  • 116
  • 137