19

Is there a way to send the information displayed by running a bash script with the -x option to a file, while not changing the standard output seen by a user running the script?

This is a debugging feature I would like to implement in a bash script we use that changes frequently.

Much appreciated.

5 Answers5

31

The output from -x goes to stderr, not stdout. But even that can be a problem -- plenty of scripts will have functional dependencies on the content of stderr, and its kind of messy to have the debug and stderr streams mixed together in some cases.

Bash versions > 4.1 do offer a different solution: the BASH_XTRACEFD environment variable allows you to specify a file descriptor which will be used to send the debug stream to. This can be a file or pipe or any other unix-y goodness you like.

# Use FD 19 to capture the debug stream caused by "set -x":
exec 19>/tmp/my-script.log
# Tell bash about it  (there's nothing special about 19, its arbitrary)
export BASH_XTRACEFD=19

# turn on the debug stream:
set -x

# run some commands:
cd /etc
find 
echo "Well, that was fun."

# Close the output:
set +x
exec 19>&-

# See what we got:
cat /tmp/my-script.log

With a bit more fiddling, you can do other things -- like doing a 'tee' on the stdout and/or stdin streams, and interleave those with the debug output, so your log is more complete. For more details on this, see https://stackoverflow.com/questions/3173131/redirect-copy-of-stdout-to-log-file-from-within-bash-script-itself.

The big advantage of this approach over alternatives is that you're not risking changes to the behavior of your script by injecting debug output into stdout or stderr.

Stabledog
  • 492
  • 4
  • 9
  • 1
    See also [here](https://stackoverflow.com/a/17030546/4414935) on how to find an unused file descriptor for the file. – jarno Oct 27 '19 at 04:54
  • Is it necessary to close the file descriptor (i.e. line `exec 19>&-`)? In my experience it is closed automatically, when the script finishes. – jarno Oct 27 '19 at 12:05
  • 1
    The code shown above does not assume that you're running a script, or how long the script might be, etc. It is true that if you just do what this snippet is doing, and put that in a script, the file handle will be closed just because the whole shell goes away at the end of the script. So in that use case, you don't strictly need to close the handle. But in general when coding against a resource, it's a good practice to close what you've opened, because then your code can be used in a larger context where housekeeping is more likely to matter. – Stabledog Oct 27 '19 at 21:23
  • Why do you export BASH_XTRACEFD? Why not just set value for it? – jarno Oct 28 '19 at 17:16
  • 1
    Anyway, an alternative way to close the file descriptor is to set new value (e.g. null) for BASH_XTRACEFD or unset it. – jarno Oct 28 '19 at 17:31
4

set -x doesn't send anything to standard output, so there's no problem there. What it does do is write to standard error, which goes to the console by default.

What you want to do is redirect stdout to another file, like this:

/bin/sh -x /my/script/file 2>/my/log/file
womble
  • 95,029
  • 29
  • 173
  • 228
1

Problem Statement:
Display the output of the script on the screen and redirect output to a file.

Resolution:
Use:
exec &> >(tee logfile)

Tee: tee is a bash command to redirect the output to a file as well as display it on the screen.

set -x: If you do not want to log the script commands in the log file (apart from the command output), you can remove set -X from the script below.

#!/bin/bash
exec &> >(tee logfile)
set -x
#start your script, I am giving this as an example
cd /home
ls
cd /var/
ls
set +x
kenlukas
  • 2,886
  • 2
  • 14
  • 25
1

See man tee.

You run it like tee commandname filename and it will display the commands output to stdout and write it also into filename.

Sven
  • 97,248
  • 13
  • 177
  • 225
0

tee command has been already mentioned but I use another alternative to merge all the outputs in the trace log. It eases the debugging, in my opinion.

The tip is to use /dev/tty with the tee command because you cannot reuse stdout/stderr filehandles anymore since they are already redirected to your trace log.

Try this:

exec 3>trace.log 2> >(tee -ia /dev/tty >&3) 1> >(tee -ia /dev/tty >&3)
BASH_XTRACEFD=3
set -x

Note: you can read synchronously with tail -f trace.log in another term.

(GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu))

juj
  • 191
  • 1
  • 2