221

I'm having some problems with some scripts in bash, about errors and unexpected behaviors. I would like to investigate the causes of the problems so I can apply fixes. Is there a way I can turn some kind of "debug-mode" for bash, to get more information?

8 Answers 8

216

Start your bash script with bash -x ./script.sh or add in your script set -x to see debug output.


Additional with bash 4.1 or later:

If you want to write the debug output to a separate file, add this to your script:

exec 5> debug_output.txt
BASH_XTRACEFD="5"

See: https://stackoverflow.com/a/25593226/3776858


If you want to see line numbers add this:

PS4='$LINENO: '


If you have access to logger command then you can use this to write debug output via your syslog with timestamp, script name and line number:

#!/bin/bash

exec 5> >(logger -t $0)
BASH_XTRACEFD="5"
PS4='$LINENO: '
set -x

# Place your code here

You can use option -p of logger command to set an individual facility and level to write output via local syslog to its own logfile.

9
  • 8
    -v can also help (prints out each line as they get executed. can be combined with -x). And see also : bashdb.sourceforge.net Sep 15, 2014 at 7:42
  • 5
    another wonderful resource is : shellcheck.net May 30, 2015 at 7:50
  • 1
    What does "exec 5>" do?
    – aggsol
    Jan 25, 2017 at 8:50
  • 5
    @aggsol: If you use BASH_XTRACEFD="5" bash writes the trace output generated when set -x is enabled to file descriptor 5. exec 5> >(logger -t $0) redirects output from file descriptor 5 to logger command.
    – Cyrus
    Jan 27, 2017 at 16:15
  • 1
    Just wondering can you get line number and shell script path or name in PS4?
    – niken
    Jul 28, 2017 at 17:22
100

Using set -x

I always use set -x and set +x. You can wrap areas that you want to see what's happening with them to turn verbosity up/down.

#!/bin/bash

set -x
..code to debug...
set +x

log4bash

Also if you've done development work and are familiar with the style of loggers that go by the names log4j, log4perl, etc., then you might want to make use of log4bash.

excerpt

Let's face it - plain old echo just doesn't cut it. log4bash is an attempt to have better logging for Bash scripts (i.e. make logging in Bash suck less).

From there you can do things like this in your Bash scripts:

#!/usr/bin/env bash
source log4bash.sh

log "This is regular log message... log and log_info do the same thing";

log_warning "Luke ... you turned off your targeting computer";
log_info "I have you now!";
log_success "You're all clear kid, now let's blow this thing and go home.";
log_error "One thing's for sure, we're all gonna be a lot thinner.";

# If you have figlet installed -- you'll see some big letters on the screen!
log_captains "What was in the captain's toilet?";

# If you have the "say" command (e.g. on a Mac)
log_speak "Resistance is futile";

Resulting in this type of output:

    ss1

log4sh

If you need something more portable there's also the older log4sh. Works similar to log4bash, available here:

2
  • On Ubuntu, I have alias say="spd-say" in my .bashrc, which imitates the say command from other distros or OS X.
    – Doorknob
    Sep 16, 2014 at 3:41
  • 2
    set -vx would be a good combination if used with trap - trap read debug. This allows you to step over line by line and see the results Jul 9, 2018 at 6:10
43

There's a bash debugger, bashdb, which is an installable package on many distros. It uses bash's built-in extended debugging mode (shopt -s extdebug). It looks a lot like gdb; here's a sample session to give some flavor:

$ ls
1st.JPG  2ndJPG.JPG
$ cat ../foo.sh
for f in *.JPG
do
  newf=${f/JPG/jpg}
  mv $f $newf
done
$ bashdb ../foo.sh
(foo.sh:1):
1:      for f in *.JPG
bashdb<0> next
(foo.sh:3):
3:        newf=${f/JPG/jpg}
bashdb<1> next
(foo.sh:4):
4:        mv $f $newf

As in gdb, the statement is shown just before it is about to be executed. So we can examine variables to see what the statement will do before it does it.

bashdb<2> print $f $newf
1st.JPG 1st.jpg
bashdb<3> next
(foo.sh:1):
1:      for f in *.JPG
bashdb<4> next
(foo.sh:3):
3:        newf=${f/JPG/jpg}
bashdb<5> next
(foo.sh:4):
4:        mv $f $newf
bashdb<6> print $f $newf
2ndJPG.JPG 2ndjpg.JPG

That's not what we want! Let's look at the parameter expansion again.

bashdb<7> print $f ${f/JPG/jpg}
2ndJPG.JPG 2ndjpg.JPG
bashdb<8> print $f ${f/JPG$/jpg}
2ndJPG.JPG 2ndJPG.JPG
bashdb<9> print $f ${f/%JPG/jpg}
2ndJPG.JPG 2ndJPG.jpg

OK, that works. Let's set newf to the correct value.

bashdb<10> eval newf=${f/%JPG/jpg}
$? is 0
bashdb<11> print $f $newf
2ndJPG.JPG 2ndJPG.jpg

Looks good. Continue the script.

bashdb<12> next
Debugged program terminated normally. Use q to quit or R to restart.
$ ls
1st.jpg  2ndJPG.jpg
0
20

The standard method to debug scripts in most Bourne-based shells, like bash is to write set -x at the top of your script. This will make bash more verbose about what's being done/executed, and how arguments are evaluated.

-x  Print commands and their arguments as they are executed.

this is useful for either, the interpreter or inside scripts. For example:

$ find "$fileloc" -type f -prune "$filename" -print
+ find /var/adm/logs/morelogs -type f -prune '-name *.user' -print
find: unknown predicate '-name *.user'
$ find "$fileloc" -type f -prune $filename -print
+ find /var/adm/logs/morelogs -type f -prune -name '*.user' -print
find: '/var/adm/logs/morelogs': No such file or directory

In the above we can see why find is failing due some single quotes.

To deactivate the feature, just type set +x.

15

Using Eclipse

You can use the combined environment of Eclipse and Shelled with the "_DEBUG.sh" script linked below.

Switching shells

By default the Shelled development tool uses /bin/dash as the interpreter. I changed this to /bin/bash to have better compatibility with most of the shell examples on the web and my environment.

NOTE: You can change this by going to: Window -> Preference -> Shell Script -> Interpreters

Setup Instructions

The Debugger package has the steps for using the _DEBUG.sh script for your script debugging which is basically (the readme.txt):

  1. Create Shell Script Project: File -> New -> Other -> Shell Script -> Shell Script Project Wizard.
  2. Create a Bash script file: File -> New -> File. For this example, it will be script.sh. Extension should be ".sh" and is a must.
  3. Copy the file _DEBUG.sh to project folder.
  4. Insert the following text to the top of the file script.sh:

    . _DEBUG.sh
    
  5. If the file is created in Microsoft Windows then be sure to execute the File -> Convert Line Delimiters To -> Unix.

  6. Set up a debug launch configuration: Run -> Debug Configurations -> Bash script... There are 2 fields to set here:

    a) "Bash script:" - Path in workspace of Eclipse to the Bash script to debug.
    e) "Debugger port:" 33333

  7. Switch to The Debug perspective. Start the debugging session. Launch script.sh from bash shell.

The bash debug UI

enter image description here

This bash debugger has the full features of standard programming debuggers such as:

  • Break Point toggle
  • Single Step-by-step operation
  • Step-in, Step-out, Step-over functions and sub-routines
  • Examining code or variables at any point while script is running

The Shelled (Shell Script Editor) IDE (Integrated Development Environment) has an added bonus of performing context checking, highlighting and indenting while writing your script. If it doesn't indent correctly, you might immediately be able to flag/local many errors there.

Then there are other IDE benefits such as:

  • TODO task list
  • Mylyn Task
  • Bookmark list
  • Multiple Window Editing
  • Remote Sharing of the environment
1
  • Cool tip. Nice to know that Bash can be debugged like this.
    – slm
    Sep 16, 2014 at 1:54
11

Nowadays, there is the VS Code Bash Debug.

https://marketplace.visualstudio.com/items?itemName=rogalmic.bash-debug

It has 'Step in/out/over' and also shows the value of each variable.

VS Code Bash Debug screenshot

1
  • this answer does not address the question in the right context; assuming command line since IDE was not mentioned
    – qodeninja
    Sep 12, 2019 at 2:54
10

simply use:

#!/bin/bash -x

the same for shell:

#!/bin/sh -x
10

A wonderful resource appeared in recent years: http://shellcheck.net

it shows you more than the regular bash does, allowing one to find easily those pesky unclosed quotes or curly brackets, etc.

Just make sure you don't paste sensitive infos (ips, passwords, etc) over the net ... (especially as it is http, unencrypted) (I believe shellcheck is also available to download, but I am not sure)

1
  • Indeed, shellcheck is eminently downloadable; the site has a link to the Github source, which has further links for downloading it for various platforms.
    – tripleee
    Jan 4 at 16:10

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .