361

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Frank
  • 64,140
  • 93
  • 237
  • 324
  • 1
    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 Jun 01 '15 at 13:49
  • @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 directly – Ken Bellows Jan 05 '17 at 17:58
  • 1
    Relevant, in particular setting of the `PS4` variable in order to define the prompt displayed as prefix of commands in tracing output: https://www.thegeekstuff.com/2008/09/bash-shell-take-control-of-ps1-ps2-ps3-ps4-and-prompt_command/ – 0 _ May 13 '21 at 06:40
  • @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](http://mywiki.wooledge.org/BashFAQ/050). If you want quotes/redirects/etc to be honored it would be `eval "$CMD"`, but that itself has [serious security risks](http://mywiki.wooledge.org/BashFAQ/048), which is why FAQ #50 _doesn't_ suggest `eval` but instead teaches use of arrays and functions. – Charles Duffy Sep 24 '21 at 20:04

4 Answers4

480
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
sehe
  • 374,641
  • 47
  • 450
  • 633
  • 2
    Perfect, here's more information about this (on a page where you may find more useful information as well): [Bash Hackers Wiki: Bash debugging tips - shell debug output](http://wiki.bash-hackers.org/scripting/debuggingtips#use_shell_debug_output) – entropo Apr 21 '11 at 22:33
  • 14
    Note also `bash -v` / `set -v` which is slightly different, and slightly less verbose. – tripleee Aug 12 '13 at 05:35
  • 3
    [It is POSIX](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_25). – Ciro Santilli OurBigBook.com Mar 13 '14 at 08:41
  • 1
    Also it may be usefull the kind of "logical brackets": `OPT=$-` to save all the keys, and `set -$OPT` to restore. – Tomilov Anatoliy Jul 10 '14 at 03:39
  • The `xtrace` option works well but in my environment hitting tab to autocomplete results in a dump of completion file contents. Is there a way to turn on without this side effect? – user1330734 Jun 22 '16 at 23:55
  • 1
    It doesn't dump the content. It traces the actions it executes to complete. You could disable bash_completion.d while tracing (you will still have filename completion as that's built into bash) @user1330734 – sehe Jun 23 '16 at 00:04
  • 1
    To elaborate on `set -v`, it prints the raw line of Bash, before variable substitution. – Steve Bennett Jan 11 '18 at 03:17
157

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
VDarricau
  • 2,699
  • 2
  • 19
  • 24
121

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

set -x

Or run it explicitly as bash -x myscript.

geekosaur
  • 59,309
  • 11
  • 123
  • 114
43

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.

Community
  • 1
  • 1
Edouard Thiel
  • 5,878
  • 25
  • 33
  • 1
    This is a neat way – PK.Shrestha Jul 02 '19 at 13:43
  • 4
    I like this better than -x since you can do something like trap 'echo "# $(date) $BASH_COMMAND"' DEBUG and it's shows time taken to execute each command inside a bash script. Thanks! – PK.Shrestha Jul 02 '19 at 13:43
  • 5
    Neat trick. For the uninitialized (like myself), use this command to reset trap back to normal mode: ```trap - DEBUG``` – JJLL Nov 01 '19 at 21:01