305
votes

What is the best way to set up a Bash script that prints each command before it executes it?

That would be great for debugging purposes.

I already tried this:

CMD="./my-command --params >stdout.txt 2>stderr.txt"
echo $CMD
`$CMD`

It's supposed to print this first:

./my-command --params >stdout.txt 2>stderr.txt

And then execute ./my-command --params, with the output redirected to the files specified.

4
I think you would have better luck if you changed `$CMD` to "$CMD". When bash encounters `$CMD`, it replaces $CMD with the command and you're left with `./my-command --params >stdout.txt 2>stderr.txt`. Since this is wrapped in backticks, bash will execute the command, and instead of printing the output to stdout, it will replace the expression including the backticks with the output of the command, and since this ends up in the beginning of a command line, bash will try to interpret the output as a new command, which is not what you want.HelloGoodbye
@HelloGoodbye In fact, don't even use the quotes. With quotes, bash tries to interpret the entire string as the name of the command; without quotes bash expands it properly and treats it as a command followed by args, redirects, etc., exactly as it would usually interpret it if you typed it in directlyKen Bellows
Relevant, in particular setting of the PS4 variable in order to define the prompt displayed as prefix of commands in tracing output: thegeekstuff.com/2008/09/…Ioannis Filippidis
@KenBellows, that's untrue. Unquoted expansion is not the same as evaluation as code: Quotes aren't honored, redirections aren't honored -- all that syntax is treated as literals. The only parsing steps that happen are string splitting and glob expansion. This is the core topic of BashFAQ #50. If you want quotes/redirects/etc to be honored it would be eval "$CMD", but that itself has serious security risks, which is why FAQ #50 doesn't suggest eval but instead teaches use of arrays and functions.Charles Duffy

4 Answers

407
votes
set -o xtrace

or

bash -x myscript.sh

This works with standard /bin/sh as well IIRC (it might be a POSIX thing then)

And remember, there is bashdb (bash Shell Debugger, release 4.0-0.4)


To revert to normal, exit the subshell or

set +o xtrace
112
votes

The easiest way to do this is to let bash do it:

set -x

Or run it explicitly as bash -x myscript.

111
votes

set -x is fine, but if you do something like:

set -x;
command;
set +x;

it would result in printing

+ command
+ set +x;

You can use a subshell to prevent that such as:

(set -x; command)

which would just print the command.

38
votes

set -x is fine.

Another way to print each executed command is to use trap with DEBUG. Put this line at the beginning of your script :

trap 'echo "# $BASH_COMMAND"' DEBUG

You can find a lot of other trap usages here.