2

Bash is able to trace execution script commands with the -x command line option. Each command is then output to stderr, prefixed by PS4 as stated in the man page.

 -x      After expanding each simple command, for command,  case  command,
         select  command,  or arithmetic for command, display the expanded
         value of PS4, followed by the command and its expanded  arguments
         or associated word list.

Albeit useful, the resolved executable path of each command is not given. So I have to figure out which executable has been run by guessing shell aliases and PATH value while reading execution trace.

For example, ruby has multiple command candidates on my workstation:

$ type -a ruby
ruby is /home/cbliard/.rvm/rubies/ruby-2.1.0/bin/ruby
ruby is /home/cbliard/.rvm/bin/ruby
ruby is /usr/bin/ruby
ruby is /home/cbliard/.rvm/bin/ruby

If executing this script with /bin/bash -x

#!/bin/bash
echo "compute"
ruby script/run_something.rb
echo "deploy"
bundle install
bundle exec cap deploy

Trace execution will be

+ echo "compute"
compute
+ ruby script/run_something.rb
[output of ruby script]
+ bundle install
Using rake (10.1.0)
Using i18n (0.6.9)
[...]
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.
+ bundle exec cap deploy
    triggering load callbacks
  * 2014-01-14 09:12:42 executing `deploy'
  * 2014-01-14 09:12:42 executing `deploy:update'
[...]
Finished: SUCCESS

I had some oddities with resolved executables, mainly due to my usage of rvm. The output I would like is with fully resolved path, like this:

+ [builtin] echo "compute"
compute
+ /home/cbliard/.rvm/rubies/ruby-1.9.3-p484/bin/ruby script/run_something.rb
[output of ruby script]
+ /home/cbliard/.rvm/gems/ruby-1.9.3-p484@global/bin/bundle install
Using rake (10.1.0)
Using i18n (0.6.9)
[...]
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.
+ /home/cbliard/.rvm/gems/ruby-1.9.3-p484@global/bin/bundle exec cap deploy
    triggering load callbacks
  * 2014-01-14 09:12:42 executing `deploy'
  * 2014-01-14 09:12:42 executing `deploy:update'
[...]
Finished: SUCCESS

Having the resolved command would really help me debugging weird rvm/bundler issues. The -x option helps a lot but sometimes it is not enough. Is there any trick that helps knowing the full characteristics of the commands while running a bash script?. It is better if the original script does not have to be modified.

5
  • You could give your script such a command line option.
    – goldilocks
    Jan 13, 2014 at 14:07
  • Yes, but doing it without modifying the script would be better. I saw on binfalse.de/2012/09/howto-debug-bash-scripts that PS4 can be customized. Maybe it is possible to customize this prompt more and make it display the full executable path.
    – cbliard
    Jan 13, 2014 at 14:25
  • @rhamu and other closers, before voting to close, please add a comment to say what you find unclear in the question. I personally find it as clear as can be, so wouldn't know how to improve it (and I was not the one asking the question). Jan 14, 2014 at 8:00
  • I am currently editing my question to make it clearer
    – cbliard
    Jan 14, 2014 at 8:02
  • @rhamu & others Is it better like this? How can I improve?
    – cbliard
    Jan 14, 2014 at 8:25

1 Answer 1

4

As an approximation, you could do:

trap '{ type -p -- "${BASH_COMMAND%% *}" >&3; } 3>&2 2> /dev/null' DEBUG
set -o functrace -o xtrace

The DEBUG trap is run before every command. During the execution of that trap, $BASH_COMMAND is set to the current command. That includes function calls, builtins, assignments... We call type -p on the first part of that up to the first space character with type's stdout retdirected to the shell's stderr (via fd 3, so that the xtrace output is redirected to /dev/null).

That's an approximation in that it won't work in cases like "cmd" foo or $CMD foo. Like for xtrace, beware of redirections of stderr.

Without modifying the script:

BASH_ENV=<(cat <<'EOF'
  trap '{ type -p -- "${BASH_COMMAND%% *}" >&3; } 3>&2 2> /dev/null' DEBUG
EOF) bash -o functrace -x your-script

Or to have it in the PS4 prompt:

BASH_ENV=<(cat <<'EOF'
  { trap '{ cmdpath=$(type -p -- "${BASH_COMMAND%% *}")
      } 2> /dev/null' DEBUG;} 2> /dev/null
EOF) PS4='+[$cmdpath] ' bash -o functrace -x your-script

or to avoid the fork:

BASH_ENV=<(cat <<'EOF'
  { trap '{ hash -- "${BASH_COMMAND%% *}";} 2> /dev/null' DEBUG;}2>/dev/null
EOF) PS4='+[${BASH_CMDS[${BASH_COMMAND%% *}]}] ' bash -o functrace -x your-script
1
  • Useful hack. Thanks! I used type without the -p option.
    – cbliard
    Jan 13, 2014 at 16:29

You must log in to answer this question.

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