I have a shell script with set -x to have verbose/debug output:

#!/bin/bash

set -x
command1
command2
...

The output looks like this:

+ command1
whatever output from command1
+ command2
whatever output from command2

My problem is, the shell output (caused by set -x) goes to the stderr, mixed with the output of the commands (command1, command2, ...). I would be happy to have the "normal" output on the screen (like the script woud run without set -x) and the "extra" output of bash separately in a file.

So I would like to have this on the screen:

whatever output from command1
whatever output from command2

and this in a log file:

+ command1
+ command2

(also fine if the log file has everything together)

The set -x 2> file obviously doens't take the right effect, because it's not the output of the set command, but it change the behaviour of the bash.

Using bash 2> file for the entire script also doesn't do the right thing, because it redirects the stderr of every command which run in this shell as well, so I don't see the error message of the commands.


Based on this ServerFault answer Send bash -x output to logfile without interupting standard output, modern versions of bash include a BASH_XTRACEFD specifically for specifying an alternate file descriptor for the output of set -x

So for example you can do

#!/bin/bash

exec 19>logfile
BASH_XTRACEFD=19

set -x
command1
command2
...

to send the output of set -x to file logfile while preserving regular standard output and standard error streams for the following commands.

Note the use of fd 19 is arbitrary - it just needs to be an available descriptor (i.e. not 0, 1, 2 or another number that you have already allocated).


Steeldriver gave you one approach. Alternatively, you can simply redirect STDERR to a file:

script.sh 2> logfile

That, however, means that both the output created by the set -x option and any other error messages produced will go to the file. Steeldriver's solution will only redirect the set -x output which is probably what you want.


After more than a year I've found the right solution to have both the "normal" output (stdout + stderr - bash trace) on the screen and all together (stdout + stderr + bash trace) in a file (bash.log):

exec   > >(tee -ia bash.log)
exec  2> >(tee -ia bash.log >& 2)
exec 19> bash.log

export BASH_XTRACEFD="19"
set -x

command1
command2

Automatic File Descriptor

Just to improve slightly on the accepted answer, and I'll keep it intact, here you can use the Bash 4.1+ automatic file descriptor allocation {any_var} to use the lowest available file descriptor - no need to hard-code it.

exec  1> >(tee -ia bash.log)
exec  2> >(tee -ia bash.log >& 2)

# Notice no leading $
exec {FD}> bash.log

# If you want to append instead of wiping previous logs
exec {FD}>> bash.log

export BASH_XTRACEFD="$FD"
set -x

# Just for fun, add this to show the file descriptors in this context
# and see the FD to your bash.log file
filan -s

command1
command2