Redirect stderr and stdout in Bash

后端 未结 15 918
日久生厌
日久生厌 2020-11-22 08:18

I want to redirect both stdout and stderr of a process to a single file. How do I do that in Bash?

15条回答
  •  误落风尘
    2020-11-22 08:54

    # Close STDOUT file descriptor
    exec 1<&-
    # Close STDERR FD
    exec 2<&-
    
    # Open STDOUT as $LOG_FILE file for read and write.
    exec 1<>$LOG_FILE
    
    # Redirect STDERR to STDOUT
    exec 2>&1
    
    echo "This line will appear in $LOG_FILE, not 'on screen'"
    

    Now, simple echo will write to $LOG_FILE. Useful for daemonizing.

    To the author of the original post,

    It depends what you need to achieve. If you just need to redirect in/out of a command you call from your script, the answers are already given. Mine is about redirecting within current script which affects all commands/built-ins(includes forks) after the mentioned code snippet.


    Another cool solution is about redirecting to both std-err/out AND to logger or log file at once which involves splitting "a stream" into two. This functionality is provided by 'tee' command which can write/append to several file descriptors(files, sockets, pipes, etc) at once: tee FILE1 FILE2 ... >(cmd1) >(cmd2) ...

    exec 3>&1 4>&2 1> >(tee >(logger -i -t 'my_script_tag') >&3) 2> >(tee >(logger -i -t 'my_script_tag') >&4)
    trap 'cleanup' INT QUIT TERM EXIT
    
    
    get_pids_of_ppid() {
        local ppid="$1"
    
        RETVAL=''
        local pids=`ps x -o pid,ppid | awk "\\$2 == \\"$ppid\\" { print \\$1 }"`
        RETVAL="$pids"
    }
    
    
    # Needed to kill processes running in background
    cleanup() {
        local current_pid element
        local pids=( "$$" )
    
        running_pids=("${pids[@]}")
    
        while :; do
            current_pid="${running_pids[0]}"
            [ -z "$current_pid" ] && break
    
            running_pids=("${running_pids[@]:1}")
            get_pids_of_ppid $current_pid
            local new_pids="$RETVAL"
            [ -z "$new_pids" ] && continue
    
            for element in $new_pids; do
                running_pids+=("$element")
                pids=("$element" "${pids[@]}")
            done
        done
    
        kill ${pids[@]} 2>/dev/null
    }
    

    So, from the beginning. Let's assume we have terminal connected to /dev/stdout(FD #1) and /dev/stderr(FD #2). In practice, it could be a pipe, socket or whatever.

    • Create FDs #3 and #4 and point to the same "location" as #1 and #2 respectively. Changing FD #1 doesn't affect FD #3 from now on. Now, FDs #3 and #4 point to STDOUT and STDERR respectively. These will be used as real terminal STDOUT and STDERR.
    • 1> >(...) redirects STDOUT to command in parens
    • parens(sub-shell) executes 'tee' reading from exec's STDOUT(pipe) and redirects to 'logger' command via another pipe to sub-shell in parens. At the same time it copies the same input to FD #3(terminal)
    • the second part, very similar, is about doing the same trick for STDERR and FDs #2 and #4.

    The result of running a script having the above line and additionally this one:

    echo "Will end up in STDOUT(terminal) and /var/log/messages"
    

    ...is as follows:

    $ ./my_script
    Will end up in STDOUT(terminal) and /var/log/messages
    
    $ tail -n1 /var/log/messages
    Sep 23 15:54:03 wks056 my_script_tag[11644]: Will end up in STDOUT(terminal) and /var/log/messages
    

    If you want to see clearer picture, add these 2 lines to the script:

    ls -l /proc/self/fd/
    ps xf
    

提交回复
热议问题