2

I want to copy stdout to a log file from within a bash script, meaning I don't want to call the script with output piped to tee, I want the script itself to handle it. I've successfully used this answer to accomplish this, using the following code:

#!/bin/bash
exec > >(sed "s/^/[${1}] /" | tee -a myscript.log)
exec 2>&1

# <rest of script>
echo "hello"
sleep 10
echo "world"

This works, but has the downside of output being buffered until the script is completed, as is also discussed in the linked answer. In the above example, both "hello" and "world" will show up in the log only after the 10 seconds have passed.

I am aware of the stdbuf command, and if running the script with

stdbuf -oL ./myscript.sh 

then stdout is indeed continuously printed both to the file and the terminal. However, I'd like this to be handled from within the script as well. Is there any way to combine these two solutions? I'd rather not resort to a wrapper script that simply calls the original script enclosed with "stdbuf -oL".

Community
  • 1
  • 1
JHH
  • 8,567
  • 8
  • 47
  • 91

2 Answers2

1

You can use a workaround and make the script execute itself with stdbuf, if a special argument is present:

#!/bin/bash

if [[ "$1" != __BUFFERED__ ]]; then
    prog="$0"
    stdbuf -oL "$prog" __BUFFERED__ "$@"
else
    shift #discard __BUFFERED__

    exec > >(sed "s/^/[${1}] /" | tee -a myscript.log)
    exec 2>&1

    # <rest of script>
    echo "hello"
    sleep 1
    echo "world"
fi

This will mostly work:

  • if you run the script with ./test, it shows unbuffered [] hello\n[] world.
  • if you run the script with ./test 123 456, it shows [123] hello\n[123] world like you want.
  • it won't work, however, if you run it with bash test - $0 is set to test which is not your script. Fixing this is not in the scope of this question though.
rr-
  • 14,303
  • 6
  • 45
  • 67
  • Thanks. Those limitations are indeed ok. Tbh it's perhaps not the cleanest solution :-) but I'll try it. – JHH Jun 22 '15 at 18:21
0

The delay in your first solution is caused by sed, not by tee. Try this instead:

#!/bin/bash
exec 6>&1 2>&1>&>(tee -a myscript.log)

To "undo" the tee effect:

exec 1>&6 2>&6 6>&-
Eugeniu Rosca
  • 5,177
  • 16
  • 45
  • Thanks. Are you sure about tee vs sed though? Pretty sure the same thing happened without sed, but I'll double check. Also, would you mind explaining what your suggestion does, what's with the 6? – JHH Jun 22 '15 at 18:23
  • I ran into a [similar](http://stackoverflow.com/questions/30786082/convert-expect-output-from-dos-to-unix-style-in-realtime) issue (not fully connected to yours at first sight), in which I was trying to `dos2unix` the output of a certain process by piping its stdout to sed/perl/tr/dos2unix/etc and duplicating it using `tee` simultaneously. I could not get rid of the annoying delay/buffering effect when both enabled. I eventually decided to `tee` *only* and to perform `dos2unix` later. More info on redirecting stdout/stderr to fd6 [here](http://www.tldp.org/LDP/abs/html/x17974.html) – Eugeniu Rosca Jun 22 '15 at 18:35
  • the `-u, --unbuffered` option on `sed` should, as the `help` says, `load minimal amounts of data from the input files and flush the output buffers more often`. – Lohmar ASHAR Feb 25 '19 at 14:34