Copy *unbuffered* stdout to file from within bash script itself

916 views Asked by At

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

2

There are 2 answers

1
rr- On

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.
3
Eugeniu Rosca On

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