Bash – Suppress the bash execution trace (set -x) from the outside of the script

bashsetshell

I tried finding an answer to this question, but got no luck so far:

I have a script that runs some other scripts, and many of those other scripts have "set -x" in them, which makes them print every command they execute. I would like to get rid of that but retain the information if any of the scripts send the error message to stderr.

So I can't simply write ./script 2>/dev/null

Also, I don't have privileges to edit those other scripts, so I can't manually change the set option.

I was thinking about logging everything from stderr to the separate file and filtering out the tracing commands, but maybe there is a simpler way?

Best Answer

With bash 4.1 and above, you can do

BASH_XTRACEFD=7 ./script.bash 7> /dev/null

(also works when bash is invoked as sh).

Basically, we're telling bash to output the xtrace output on file descriptor 7 instead of the default of 2, and redirect that file descriptor to /dev/null. The fd number is arbitrary. Use a fd above 2 that is not otherwise used in your script. If the shell you're entering this command in is bash or yash, you can even use a number above 9 (though you may run into problems if the file descriptor is used internally by the shell).

If the shell you're calling that bash script from is zsh, you can also do:

(export BASH_XTRACEFD; ./script.bash {BASH_XTRACEFD}> /dev/null)

for the variable to be automatically assigned the first free fd above 9.

For older versions of bash, another option, if the xtrace is turned on with set -x (as opposed to #! /bin/bash -x or set -o xtrace) would be to redefine set as an exported function that does nothing when passed -x (though that would break the script if it (or any other bash script it invokes) used set to set the positional parameters).

Like:

set()
  case $1 in
    (-x) return 0;;
    (-[!-]|"") builtin set "$@";;
    (*) echo >&2 That was a bad idea, try something else; builtin set "$@";;
  esac

export -f set
./script.bash

Another option is to add a DEBUG trap in a $BASH_ENV file that does set +x before every command.

echo 'trap "{ set +x; } 2>/dev/null" DEBUG' > ~/.no-xtrace
BASH_ENV=~/.no-xtrace ./script.bash

That won't work when set -x is done in a sub-shell though.

As @ilkkachu said, provided you have write permission to any folder on the filesystem, you should at least be able to make a copy of the script and edit it.

If there's nowhere you can write a copy of the script, or if it's not convenient to make and edit a new copy every time there's an update to the original script, you may still be able to do:

 bash <(sed 's/set -x/set +x/g' ./script.bash)

That (and the copy approach) may not work properly if the script does anything fancy with $0 or special variables like $BASH_SOURCE (such as looking for files that are relative to the location of the script itself), so you may need to do some more editing like replace $0 with the path of the script...

Related Question