Why does `exec 2>&1` fail in this bourne shell script?

10,987

Solution 1

Your script is not failing - it's working just fine. Your understanding is correct in that exec >logfile; exec 2>&1 redirects both standard output and standard error to logfile. So, you should be looking in the log file and not your terminal for output and error. If you perform those redirections directly in your current shell, it looks like your shell has frozen because you sent all the output away from your terminal.

Note that the output from the xtrace (set -x) option also goes to standard error, which is always file descriptor 2 ... which you sent away to the log file. You should find the rest of it after the exec 2>&1 in there.

Solution 2

This form of exec (i.e. without a command) is used to redirect all subsequent output from the current shell interpreter.

from bash's built-in help:

$ help exec
exec: exec [-cl] [-a name] [command [arguments ...]] [redirection ...]
    Replace the shell with the given command.

    Execute COMMAND, replacing this shell with the specified program.
    ARGUMENTS become the arguments to COMMAND.  If COMMAND is not specified,
    any redirections take effect in the current shell.
    [...]

I use exec &> logfile to redirect stdout and stderr at the same time. e.g. most of my backup and rsync wrapper scripts (or any script that produces lots of output that I might want to examine in detail later) start with something like this:

BNAME=$(basename "$0" .sh)
LOGFILE="/tmp/$BNAME.log"
savelog "$LOGFILE"
exec &> "$LOGFILE"

I then run the script from cron, or in the background and use tail -F to watch the logfile as the script is running. The savelog lets me keep the output from the last 7 runs (7 by default, savelog -c can be used to change that).

Share:
10,987

Related videos on Youtube

Mark Norgren
Author by

Mark Norgren

Updated on September 18, 2022

Comments

  • Mark Norgren
    Mark Norgren over 1 year

    I am porting an old ksh script to Bourne shell. The old ksh script contains the following code:

    #!/bin/sh
    
    tmpLog=/var/tmp/logfile.$$
    
    exec 1> $tmpLog
    exec 2>&1
    
    eval $*
    another_command_1
    another_command_2
    

    From what I read, it seems that these two exec statements are intended to execute $*, another_command_1, another_command_2 and all following commands; and then redirect all STDERR and STDOUT from those commands into /var/tmp/logfile.$$. However, when I run this in a script the script fails after exec 2>&1.

    stefanl@host:~ $ sh -xv ./output.sh echo "Hello"
    #!/bin/sh
    
    tmpLog=/var/tmp/logfile.$$
    + tmpLog=/var/tmp/logfile.39918
    
    exec 1> $tmpLog
    + exec
    exec 2>&1
    + exec
    stefanl@host:~ $
    

    And when I run this on the commandline, my shell freezes after I execute exec 2>&1:

    stefanl@host:~ $ tmpLog=/var/tmp/logfile.$$
    stefanl@host:~ $ exec 1> $tmpLog
    stefanl@host:~ $ exec 2>&1
    ### FREEZE ###
    

    My questions:

    1. What is exec 2>&1 supposed to do?
    2. Why is it failing for me?
  • Mark Norgren
    Mark Norgren almost 12 years
    Ah, thanks! And if I open a new window and do a tail -f /var/tmp/logfile.39918 I see everything that I type in the old window. Clever trick!
  • jw013
    jw013 almost 12 years
    The question specifically targets the Bourne shell. Bourne doesn't have the &> operator, nor is quoting the bash man page terribly helpful. The part about cron is irrelevant.
  • Alessio
    Alessio almost 12 years
    It still clearly answered his first question 'What is exec 2>&1 supposed to do?` which hadn't actually been answered yet. Assuming a reasonable ability to understand, generalise and extrapolate doesn't deserve a -1. I tend to prefer providing a general explanation instead of just cargo-cult magic to a specific question. BTW, it was the bash help, not the bash man page...it just happened to be the most convenient and succinct documentation to hand, and the relevant point about taking effect in the current shell applies to all versions of sh.
  • jw013
    jw013 almost 12 years
    I don't see any part in your answer that specifically explains what exec 2>&1 does? I generally downvote answers that encourage misconceptions, whether explicitly or implicitly (in your case, implying sh = bash), because not every person who reads your answer in the future will know the difference. bash is based on sh but in practice there are many differences (like the &>: putting that in a Bourne shell script will probably break it). Also, none of those options that bash exec accepts are likely to be valid.
  • jw013
    jw013 almost 12 years
    That sentence is no better than "exec >logfile; exec 2>&1 redirects both standard output and standard input to logfile". You're still confusing bash with sh.
  • Alessio
    Alessio almost 12 years
    Q. "What is exec 2>&1 supposed to do?". A. "This form of exec (i.e. without a command) is used to [...]". There is no confusion between bash and sh in that sentence. It is true for all variants of sh. You ignored his first question and answered his second, I ignored his second and answered his first. You're judging my answer by the question you answered, not by the one I answered.
  • Kevin Fegan
    Kevin Fegan over 10 years
    "... sends both standard output and standard input to logfile, ..." should be "standard output and standard error". I'll edit to make the correction.
  • Yu Shen
    Yu Shen about 7 years
    I appreciate your help! 'exec &> logfile' is the most useful information. The idea of extracting script basename 'BNAME=$(basename "$0" .sh)` is also good, but it's not relevant to the topic. It might be better to separate the additional help as comments.