23

I have a bash script with lots of output and I would like to change that script (not create a new one) to copy all the output to a file, just like it would happen if I were piping it through tee.

I have a file script.sh:

#!/bin/bash
init
do_something_that_outputs_stuff_to_STDOUT
launch_applications_that_output_to_STDOUT
fini

and I would like to make a copy of STDOUT to a file script.log without having to type ./script.sh | tee script.log every time.

Thanks, Tom

1
  • I am sorry for not saying this earlier, but the script is rather long, which is why I'm looking for a simple solution. Appending | tee to each line is a bit too much.
    – Tom
    Commented Dec 24, 2009 at 15:27

7 Answers 7

16

I couldn't get Dennis' very simple one-liner to work, so here's a far more convoluted method. I'd try his first.

As mentioned, you can use exec to redirect standard error & standard out for the entire script. Like so:
exec > $LOGFILE 2>&1 This will output all stderr and stdout to $LOGFILE.

Now, since you want to have this displayed to the console as well as a logfile, you're also going to have to use a named pipe for exec to write to, and tee to read from.
(Dennis' one-liner technically does this as well, although obviously in a different way) The pipe itself is created with mkfifo $PIPEFILE. Then do the following.

# Start tee writing to a logfile, but pulling its input from our named pipe.
tee $LOGFILE < $PIPEFILE &

# capture tee's process ID for the wait command.
TEEPID=$!

# redirect the rest of the stderr and stdout to our named pipe.
exec > $PIPEFILE 2>&1

echo "Make your commands here"
echo "All their standard out will get teed."
echo "So will their standard error" >&2

# close the stderr and stdout file descriptors.
exec 1>&- 2>&-

# Wait for tee to finish since now that other end of the pipe has closed.
wait $TEEPID

If you want to be thorough, you can create and destroy the named pipe file at the start and end of your script.

For the record, I gleaned most of this from a random guy's very informative blog post: (Archived version)

2
  • This doesn't seem to work in cygwin. :-(
    – docwhat
    Commented Sep 8, 2010 at 17:53
  • This post does a good job of educating. Thank you very much. Commented Jun 24, 2014 at 1:58
32

Simply add this to the beginning of your script:

exec > >(tee -ia script.log)

That will append all output sent to stdout to the file script.log, leaving the previous contents in place. If you want to start fresh every time the script is run just add rm script.log right before that exec command or remove the -a from the tee command.

The -i option causes tee to ignore interrupt signals and may allow tee to catch a more complete output set.

Add this line to also catch all errors (in a separate file):

exec 2> >(tee -ia scripterr.out)

The spaces between the multiple > symbols are important.

12
  • Very interesting solution.
    – raphink
    Commented Dec 23, 2009 at 16:35
  • 2
    This is a really elegant way of doing it, compared to all the crap I posted. But when I create a script with those two lines in it, it hangs, and never exits properly. Commented Dec 23, 2009 at 17:00
  • What version of Bash? Commented Dec 23, 2009 at 17:23
  • GNU bash, version 3.2.25(1) as part of a RHEL5.3 install. It appears this is the latest version from the official repositories. Commented Dec 23, 2009 at 19:15
  • I just tried it under cygwin in Bash 3.2.49(23)-release and got file descriptor errors. I works in Bash 4.0.33(1)-release in Ubuntu 9.10, though, so it may be a Bash 4 thing. Commented Dec 23, 2009 at 19:55
7

This is combined version of answer posted by Dennis Williamson earlier. Appends both err & std out to script.log in right order.

Add this line to the beginning of your script:

exec > >(tee -a script.log) 2>&1

Note the space between the > signs. Works for me on GNU bash, version 3.2.25(1)-release (x86_64-redhat-linux-gnu)

7

I suppose another approach is something like this:

#!/bin/bash

# start grouping at the top
{ 
    # your script goes here
    do_something_that_outputs_stuff_to_STDOUT
    launch_applications_that_output_to_STDOUT

# and at the bottom, redirect everything from the grouped commands
} 2>&1 | tee script.log 
1
3

If you want a copy of the output, you can use tee this way:

  #!/bin/bash
  init
  do_something_that_outputs_stuff_to_STDOUT | tee script.log
  launch_applications_that_output_to_STDOUT | tee -a script.log
  fini

This will only log stdout to script.log however. If you want to make sure both the stderr and stdout are redirected, use:

  #!/bin/bash
  init
  do_something_that_outputs_stuff_to_STDOUT 2>&1 | tee script.log
  launch_applications_that_output_to_STDOUT 2>&1 | tee -a script.log
  fini

You could even make it a bit nicer with a little function:

  #!/bin/bash

  LOGFILE="script.log"
  # Wipe LOGFILE if you don't want to add to it at each run
  rm -f "$LOGFILE"

  function logcmd() {
        local cmd="$1" logfile=${LOGFILE:-script.log} # Use default value if unset earlier

        # Launch command, redirect stderr to stdout and append to $logfile
        eval '$cmd' 2>&1 | tee -a "$logfile"
  }

  init
  logcmd "do_something_that_outputs_stuff_to_STDOUT"
  logcmd "launch_applications_that_output_to_STDOUT"
  fini
2
  • Good answer. If he really only cares about stdout, I would just leave the 2>&1 out and just pipe it to tee. Commented Dec 23, 2009 at 16:14
  • That's true, I only included stderr because the previous answer from Igor cared to redirect it and I thought it was a good idea.
    – raphink
    Commented Dec 23, 2009 at 16:16
1

You'd just use this:

script logfile.txt
your script or command here

This outputs everything to that log, everything, as well as displaying it on screen. If you want the FULL output, this is how to do it.

And you can then replay the script if you are lazy.

1
  • Might be good to note that script is a package, eg. sudo apt-get install script to install it on Debian flavored distros, additionally script -ac 'command opts' ~/Documents/some_log.script may be reviewed within a terminal via something like strings ~/Documents/some_log.script | more; strings be a simple way to pre-sanitize some of the things that script injects to allow for fancier methods of re-play/view. Otherwise a solid answer @Phishy, because script also preserves interactive things too.
    – S0AndS0
    Commented May 14, 2019 at 5:56
0
#!/bin/bash
init
do_something_that_outputs_stuff_to_STDOUT > script.log 2>&1
launch_applications_that_output_to_STDOUT >> script.log 2>&1
fini

You can see more about how it works here: http://www.xaprb.com/blog/2006/06/06/what-does-devnull-21-mean/

3
  • 1
    I added a second > to the second line in Igor's answer so that it doesn't wipeout the log written in the first line. Commented Dec 23, 2009 at 15:57
  • 1
    He wants a copy though, so he needs to use tee.
    – raphink
    Commented Dec 23, 2009 at 15:59
  • @Raphink: That's right :)
    – Tom
    Commented Dec 24, 2009 at 15:25

You must log in to answer this question.

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