0 0 0 0


I have a script which will be run interactively by non-technical users. The script writes status updates to STDOUT so that the user can be sure that the script is running OK.

I want both STDOUT and STDERR redirected to the terminal (so that the user can see that the script is working as well as see if there was a problem). I also want both streams redirected to a log file.

I've seen a bunch of solutions on the net. Some don't work and others are horribly complicated. I've developed a workable solution (which I'll enter as an answer), but it's kludgy.

The perfect solution would be a single line of code that could be incorporated into the beginning of any script that sends both streams to both the terminal and a log file.

EDIT: Redirecting STDERR to STDOUT and piping the result to tee works, but it depends on the users remembering to redirect and pipe the output. I want the logging to be fool-proof and automatic (which is why I'd like to be able to embed the solution into the script itself.)

Best Answer:

A year later, here's an old bash script for logging anything. For example,
teelog make ... logs to a generated log name (and see the trick for logging nested makes too.)

Version="2008-10-9 oct denis-bz"
Help() {
cat <<!
    $me anycommand args ...
logs the output of "anycommand ..." as well as displaying it on the screen,
by running
    anycommand args ... 2>&1 | tee `day`-command-args.log
That is, stdout and stderr go to both the screen, and to a log file.
(The Unix "tee" command is named after "T" pipe fittings, 1 in -> 2 out;
see ).
The default log file name is made up from "command" and all the "args":
    $me cmd -opt dir/file  logs to `day`-cmd--opt-file.log .
To log to xx.log instead, either export log=xx.log or
    $me log=xx.log cmd ...
If "logdir" is set, logs are put in that directory, which must exist.
An old xx.log is moved to /tmp/$USER-xx.log .
The log file has a header like
    # from: command args ...
    # run: date pwd etc.
to show what was run; see "From" in this file.
Called as "Log" (ln -s $me Log), Log anycommand ... logs to a file:
    command args ... > `day`-command-args.log
and tees stderr to both the log file and the terminal -- bash only.
Some commands that prompt for input from the console, such as a password,
don't prompt if they "| tee"; you can only type ahead, carefully.
To log all "make" s, including nested ones like
    cd dir1; $(MAKE)
    cd dir2; $(MAKE)
export MAKE="$me make"
  # See also: output logging in screen(1).
    exit 1
#  denisbz may2008 --
day() {  # 30mar, 3mar
    /bin/date +%e%h  |  tr '[A-Z]' '[a-z]'  |  tr -d ' '
edate() {  # 19 May 2008 15:56
    echo `/bin/date "+%e %h %Y %H:%M"`
From() {  # header  # from: $*  # run: date pwd ...
    case `uname` in Darwin )
        mac=" mac `sw_vers -productVersion`"
    cut -c -200 <<!
${comment-#} from: $@
${comment-#} run: `edate`  in $PWD `uname -n` $mac `arch` 
    # mac $PWD is pwd -L not -P real
    # log name: day-args*.log, change this if you like --
logfilename() {
    [[ $1 == "sudo" ]]  &&  shift
    for arg
        log="$log-${arg##*/}"  # basename
        (( ${#log} >= 100 ))  &&  break  # max len 100
            # no blanks etc in logfilename please, tr them to "-"
    echo $logdir/` echo "$log".log  |  tr -C '.:+=[:alnum:]_n' - `
case "$1" in
-v* | --v* )
    echo "$0 version: $Version"
    exit 1 ;;
"" | -* )
    # scan log= etc --
while [[ $1 == [a-zA-Z_]*=* ]]; do
    export "$1"
: ${logdir=.}
[[ -w $logdir ]] || {
    echo >&2 "error: $me: can't write in logdir $logdir"
    exit 1
: ${log=` logfilename "$@" `}
[[ -f $log ]]  &&
    /bin/mv "$log" "/tmp/$USER-${log##*/}"
case ${0##*/} in  # basename
log | Log )  # both to log, stderr to caller's stderr too --
    From "$@"
} > $log  2> >(tee /dev/stderr)  # bash only
    # see 47, stderr to a pipe
* )
    From "$@"  # header: from ... date pwd etc.
    "$@"  2>&1  # run the cmd with stderr and stdout both to the log
} | tee $log
    # mac tee buffers stdout ?

Copyright © 2011 Dowemo All rights reserved.    Creative Commons   AboutUs