sdaau
sdaau

Reputation: 38619

Start and stop logging terminal output to file from within bash script

Based on these three questions:

... I have put together the script testlogredir.sh, included below. What it's trying to do is: run three commands, whose stdout and stderr output will be logged both to terminal and log file; and then run two more commands, whose stdout and stderr output is only sent to terminal. In effect, it's starting and stopping redirection of the script's terminal output to logfile, while preserving the terminal output.

The interesting thing is, that if I use a sleep after stopping the log-to-file, everything works as expected:

$ bash testlogredir.sh 1 y
 --- testlogredir.sh METHOD=1 DOSLEEP=y ---
aaa
bbb
ccc
ddd
eee

$ cat test.log 
aaa
bbb
ccc

... and the same result is also obtained running bash testlogredir.sh 2 y.

Interestingly, if I do not use the sleep (same output will also be obtained with bash testlogredir.sh 1):

$ bash testlogredir.sh 2
 --- testlogredir.sh METHOD=2 DOSLEEP= ---
ddd
eee
$ aaa
bbb
ccc
^C

$ cat test.log 
aaa
bbb
ccc

... it is notable that first the last "ddd" and "eee" are output to terminal; then prompt appears then the first "aaa", "bbb", "ccc" are output - and the process as a whole (b)locks; so I have to press Ctrl-C (^C) to exit it. The logfile, however, does have the contents as expected.

I speculate that in the no sleep case, the bash interpreter ran so quickly through the script, that it managed to echo the "last" two "ddd" and "eee" first - and only then does tee output what it has stored (note that this is not due to buffering behavior of tee, as I've tried it also with stdbuf getting the same results), and apparently it is the tee that does the blocking. Thus, adding the sleep, makes the bash script "synchronize" with the tee (sub?)process, in a way.

Obviously, I'd like the command outputs to be shown in sequential order - and the sleep itself doesn't bother me that much, as I can set it to sleep 0.1 and barely notice it. But I have to ask - is this the right way to do this kind of start/stop "tee" redirection from within a bash script? In other words - is there an alternative to using sleep to achieve this kind of "synchronization", so to speak?


testlogredir.sh

#!/usr/bin/env bash

# testlogredir.sh

# defaults:
METHOD="1"  # or "2"
DOSLEEP=""  # or "y"

if [ "$1" ] ; then
  METHOD="$1" ;
fi
if [ "$2" ] ; then
  DOSLEEP="$2" ;
fi

# this should be echoed only to terminal
echo " --- $0 METHOD=$METHOD DOSLEEP=$DOSLEEP ---"
# silent remove of test.log
rm -f test.log

if [ $METHOD == "1" ] ; then
  # Redirect 3 into (use fd3 as reference to) /dev/stdout
  exec 3> /dev/stdout
  # Redirect 4 into (use fd4 as reference to) /dev/stderr
  exec 4> /dev/stderr
fi

if [ $METHOD == "2" ] ; then
  # backup the original filedescriptors, first
  # stdout (1) into fd6; stderr (2) into fd7
  exec 6<&1
  exec 7<&2
fi

# Redirect stdout ( > ) into a named pipe ( >() ) running "tee"
#~ exec > >(stdbuf -i0 -o0 tee test.log)
exec > >(tee test.log)
# Redirect stderr (2) into stdout (1)
exec 2>&1

# these should be echoed both to terminal and log
echo "aaa"
echo "bbb"
echo "ccc" >&2

if [ $METHOD == "1" ] ; then
  # close current stdout, stderr
  exec 1>&-
  exec 2>&-
  # Redirect stdout (1) and stderr (2)
  exec 1>&3
  exec 2>&1
fi

if [ $METHOD == "2" ] ; then
  # close and restore backup; both stdout and stderr
  exec 1<&6 6<&-
  exec 2<&7 2<&-
  # Redirect stderr (2) into stdout (1)
  exec 2>&1
fi

if [ "$DOSLEEP" ] ; then
  sleep 1 ;
fi

# these should be echoed only to terminal

echo "ddd"
echo "eee" >&2

exit

Upvotes: 0

Views: 5577

Answers (1)

vegatripy
vegatripy

Reputation: 160

You can use the braces to redirect commands to tee through a pipe

#!/bin/bash

# to terminal and logfile.log
{
 echo "aaa"
 echo "bbb"
 echo "ccc"
} 2>&1 | tee logfile.log

# only terminal
echo "ddd"
echo "eee"

Upvotes: 2

Related Questions