8

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.

1
8

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

4
  • Why 3> "$LOG_FILE" on the last line of the last snippet? Is it needed? – fra-san Mar 25 at 20:18
  • 1
    @fra-san, that's to truncate the file (and abort the command if the file can't be opened). Then both tees open the file again in append mode. – Stéphane Chazelas Mar 25 at 21:02
  • 1
    I generally don't use zsh's multios, but it's really nice for this. To fix the issue on the use of the same resource, one could also do exec 3> $LOG_FILE >&1 2>&2 >&3 2>&3 3>&-. – JoL Mar 25 at 23:34
  • A very instructive answer, as always. Thank you! I am puzzled, however, by the first >&1 subexpression in the expression exec >&1 > $LOG_FILE 2>&1. I thought that, if one omits the fd before the >, it defaults to 1. This would mean that >&1 is the same as 1>&1. Am I reasoning correctly? If so, what is then the point of 1>&1? Doesn't it amount to "duplicating" 1 as 1? – kjo May 1 at 17:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.