Shell Script Output – How to Redirect Output to Both Terminal and File in sh Shell

io-redirectionlogsshellshell-script

How to redirect both stdout and stderr to a file as well as terminal from inside the script.

#!/bin/sh

LOG_FILE="/tmp/abc.log"

exec &> >(tee -a $LOG_FILE)

echo "Start"
echo "Hi" 1>&2 
echo "End"

I found above script. But this script works only on bash. With sh shell it gives following error:

./abc.sh: 5: Syntax error: redirection unexpected (expecting word)

I want a script which works with both sh and bash shells.

Best Answer

Yes, &> is a bash operator (now also supported by zsh, while zsh always had >& for the same like in csh), and >(...) a ksh operator (now also supported by zsh and bash), neither are sh operator. That unquoted $LOG_FILE where you obviously don't want split+glob here, makes it zsh syntax (the only one of those shells where split+glob is not performed implicitly upon unquoted expansions, though in zsh, you'd just do exec >&1 > $LOG_FILE 2>&1).

In sh syntax, you can do:

#! /bin/sh -
LOG_FILE="/tmp/abc.log"

{

  # your script here


} 2>&1 | tee -- "$LOG_FILE"

Or put everything in a function:

#! /bin/sh -
LOG_FILE="/tmp/abc.log"

main() {

  # your script here


}

main "$@" 2>&1 | tee -- "$LOG_FILE"

In any case, both that and your zsh-style approach would end up printing error messages on the same resource as open on stdout. So if someone does your-script > out 2> err, err will be empty and out will contain both the normal output and the errors.

To make sure the original destination of output and error is preserved, you could do instead:

{
  {
    main "$@" 3>&- | tee -a -- "$LOG_FILE" >&3 3>&-
  } 2>&1 | tee -a -- "$LOG_FILE" >&2 3>&-
} 3> "$LOG_FILE" 3>&1

(untested).

Related Question