17

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?

24

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...

  • Your first answer is exactly what I needed, clean and elegant. Could you explain a little how does it work? Why the number 7? What could other numbers be used for? Thanks. – human Mar 17 '17 at 15:46
  • @human, see edit – Stéphane Chazelas Mar 17 '17 at 16:30
  • 1
    The {BASH_XTRACEFD}> trick works in bash 4.1 or later as well. – chepner Mar 17 '17 at 17:03
  • @chepner, yes the feature was added to zsh, ksh93 and bash at the same time upon a suggestion by a zsh developer. But, here it doesn't work for ksh93 or bash in that the variable is not passed in the environment of the command (compare <shell> -c 'export fd; printenv fd {fd}> /dev/null' in zsh, bash and ksh93). You could make it work in ksh93/bash by doing it in two steps or possibly using eval, but for bash, that would have side effects if the xtrace option was on. – Stéphane Chazelas Mar 17 '17 at 17:22
5

Since they're scripts, you could make copies of them, and edit those.

Other than that, filtering the output would seem simple, you could explicitly set PS4 to something more unusual than a single plus, to make the filtering easier:

PS4="%%%%" bash script.sh 2>&1 | grep -ve '^%%%%'

(of course that will collapse stdout and stdin, but piping just stderr in Bash gets a bit hairy, so I'll ignore that)

  • 1
    Piping just stderr is easy: PS4="%%%%" bash script.sh 2> >(grep -ve '^%%%%'). – Patrick Mar 17 '17 at 12:27
  • 4
    @Patrick, doing that in bash has issues as that grep is run asynchronously (bash is not waiting for it, so it can (and often does) output things after the next command in the script is started). – Stéphane Chazelas Mar 17 '17 at 12:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.