在Bash中重定向stderr和stdout

时间:2009-03-12 09:14:06

标签: bash shell redirect pipe

我想将进程的stdout和stderr重定向到单个文件。我如何在Bash中做到这一点?

15 个答案:

答案 0 :(得分:703)

看看here。应该是:

yourcommand &>filename

(将stdoutstderr重定向到文件名。

答案 1 :(得分:421)

do_something 2>&1 | tee -a some_file

这会将stderr重定向到stdout,将stdout重定向到some_file ,然后将其打印到stdout。

答案 2 :(得分:224)

您可以将 stderr 重定向到 stdout ,将 stdout 重定向到文件中:

some_command >file.log 2>&1 

请参阅http://tldp.org/LDP/abs/html/io-redirection.html

这种格式比最受欢迎的&>更受欢迎。格式只适用于bash。在Bourne shell中,它可以解释为在后台运行命令。此格式更易读2(STDERR)重定向到1(STDOUT)。

编辑:改变了评论中指出的顺序

答案 3 :(得分:180)

# 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'"

现在,简单的echo将写入$ LOG_FILE。适用于守护进程。

原帖的作者,

这取决于你需要达到的目标。如果您只需要重定向进出您从脚本调用的命令,则已经给出了答案。我的目的是在当前脚本中重定向,它会影响所提到的代码片段之后的所有命令/内置函数(包括forks)。


另一个很酷的解决方案是立即重定向到std-err / out和记录器或日志文件,这涉及将“流”拆分为两个。此功能由'tee'命令提供,该命令可以一次写入/附加到多个文件描述符(文件,套接字,管道等):tee FILE1 FILE2 ...&gt;(cmd1)&gt;(cmd2)... < / p>

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
}

所以,从一开始。假设我们有终端连接到/ dev / stdout(FD#1)和/ dev / stderr(FD#2)。在实践中,它可以是管道,插座或其他任何东西。

  • 创建FD#3和#4并分别指向与#1和#2相同的“位置”。从现在开始,改变FD#1不会影响FD#3。现在,FD#3和#4分别指向STDOUT和STDERR。这些将用作真实终端STDOUT和STDERR。
  • 1&GT; &gt;(...)将STDOUT重定向到parens中的命令
  • parens(sub-shell)从exec的STDOUT(管道)执行'tee'读取,并通过另一个管道重定向到'logger'命令到parens中的子shell。同时它将相同的输入复制到FD#3(终端)
  • 第二部分,非常相似,是关于为STDERR和FD#2和#4做同样的技巧。

运行具有上述行和另外一行的脚本的结果:

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

......如下:

$ ./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

如果您想要查看更清晰的图片,请将以下两行添加到脚本中:

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

答案 4 :(得分:36)

bash your_script.sh 1>file.log 2>&1

1>file.log指示shell将STDOUT发送到文件file.log2>&1告诉它将STDERR(文件描述符2)重定向到STDOUT(文件描述符1)。

注意:该命令非常重要,因为liw.fi指出,2>&1 1>file.log不起作用。

答案 5 :(得分:21)

奇怪的是,这有效:

yourcommand &> filename

但这会产生语法错误:

yourcommand &>> filename
syntax error near unexpected token `>'

你必须使用:

yourcommand 1>> filename 2>&1

答案 6 :(得分:12)

简答:Command >filename 2>&1Command &>filename

说明:

考虑下面的代码,它将单词“stdout”打印到stdout,将单词“stderror”输出到stderror。

$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror

请注意'&amp;' operator告诉bash 2是文件描述符(指向stderr)而不是文件名。如果我们省略'&amp;',此命令会将stdout打印到stdout,并创建一个名为“2”的文件并在那里写stderror

通过试验上面的代码,您可以准确地了解重定向运算符的工作原理。例如,通过将哪两个描述符中的哪一个1,2重定向到/dev/null,以下两行代码将从stdout中删除所有内容,并从stderror中删除所有内容(打印剩余的内容)。

$ (echo "stdout"; echo "stderror" >&2) 1>/dev/null
stderror
$ (echo "stdout"; echo "stderror" >&2) 2>/dev/null
stdout

现在,我们可以解释为什么以下代码没有产生输出的解决方案:

(echo "stdout"; echo "stderror" >&2) >/dev/null 2>&1

要真正理解这一点,我强烈建议您阅读此webpage on file descriptor tables。假设你已经完成了阅读,我们就可以继续了。请注意Bash从左到右处理;因此Bash首先看>/dev/null(与1>/dev/null相同),并将文件描述符1设置为指向/ dev / null而不是stdout。完成此操作后,Bash向右移动并看到2>&1。这将文件描述符2 设置为指向与文件描述符1相同的文件(而不是文件描述符1本身!!!!(有关详细信息,请参阅this resource on pointers))。由于文件描述符1指向/ dev / null,并且文件描述符2指向与文件描述符1相同的文件,因此文件描述符2现在也指向/ dev / null。因此,两个文件描述符都指向/ dev / null,这就是没有输出的原因。

要测试您是否真的理解这个概念,请在切换重定向顺序时尝试猜测输出:

(echo "stdout"; echo "stderror" >&2)  2>&1 >/dev/null
  

stderror

这里的推理是从左到右进行评估,Bash看到2&gt;&amp; 1,因此将文件描述符2设置为指向与文件描述符1相同的位置,即stdout。然后它设置文件描述符1(记住&gt; / dev / null = 1&gt; / dev / null)指向&gt; / dev / null,从而删除通常发送到标准输出的所有内容。因此我们剩下的就是那些没有发送到子shell中的stdout(括号中的代码) - 即“stderror”。   值得注意的是,即使1只是指向stdout的指针,通过2>&1将指针2重定向到1也不会形成指针链2 - > 1 - &gt;标准输出。如果是这样,由于将1重定向到/ dev / null,代码2>&1 >/dev/null将给出指针链2 - &gt; 1 - &gt; / dev / null,因此代码不产生任何东西,与我们上面看到的相反。

最后,我注意到有一种更简单的方法:

从第3.6.4节here开始,我们看到我们可以使用运算符&>来重定向stdout和stderr。因此,要将任何命令的stderr和stdout输出重定向到\dev\null(删除输出),我们只需键入 $ command &> /dev/null 或者在我的例子中:

$ (echo "stdout"; echo "stderror" >&2) &>/dev/null

关键要点:

  • 文件描述符的行为类似于指针(尽管文件描述符与文件指针不同)
  • 将文件描述符“a”重定向到指向文件“f”的文件描述符“b”,使文件描述符“a”指向与文件描述符b-文件“f”相同的位置。它不形成指针链a - &gt; b - &gt; ˚F
  • 由于上述原因,订单很重要,2>&1 >/dev/null是!= >/dev/null 2>&1。一个产生输出而另一个没产生!

最后看看这些优秀的资源:

Bash Documentation on RedirectionAn Explanation of File Descriptor TablesIntroduction to Pointers

答案 7 :(得分:10)

LOG_FACILITY="local7.notice"
LOG_TOPIC="my-prog-name"
LOG_TOPIC_OUT="$LOG_TOPIC-out[$$]"
LOG_TOPIC_ERR="$LOG_TOPIC-err[$$]"

exec 3>&1 > >(tee -a /dev/fd/3 | logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_OUT" )
exec 2> >(logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_ERR" )

这是相关的:写作stdOut&amp; stderr到syslog。

它几乎可以工作,但不是来自xinted;(

答案 8 :(得分:6)

我想要一个解决方案,将stdout plus stderr的输出写入日志文件,stderr仍然在控制台上。所以我需要通过tee复制stderr输出。

这是我找到的解决方案:

command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
  • 首先交换stderr和stdout
  • 然后将stdout附加到日志文件
  • 管道stderr to tee并将其附加到日志文件

答案 9 :(得分:4)

对于这种情况,当需要“管道”时,可以使用:

  

|&

例如:

echo -ne "15\n100\n"|sort -c |& tee >sort_result.txt

TIMEFORMAT=%R;for i in `seq 1 20` ; do time kubectl get pods |grep node >>js.log  ; done |& sort -h

这种基于bash的解决方案可以分别传递STDOUT和STDERR(从“ sort -c”的STDERR或从STDERR到“ sort -h”的管道)。

答案 10 :(得分:1)

“最简单”方式(仅限bash4):ls * 2>&- 1>&-

答案 11 :(得分:1)

以下功能可用于自动切换beetwen stdout / stderr和日志文件的输出过程。

#!/bin/bash

    #set -x

    # global vars
    OUTPUTS_REDIRECTED="false"
    LOGFILE=/dev/stdout

    # "private" function used by redirect_outputs_to_logfile()
    function save_standard_outputs {
        if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
            echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
            exit 1;
        fi
        exec 3>&1
        exec 4>&2

        trap restore_standard_outputs EXIT
    }

    # Params: $1 => logfile to write to
    function redirect_outputs_to_logfile {
        if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
            echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
            exit 1;
        fi
        LOGFILE=$1
        if [ -z "$LOGFILE" ]; then
            echo "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"

        fi
        if [ ! -f $LOGFILE ]; then
            touch $LOGFILE
        fi
        if [ ! -f $LOGFILE ]; then
            echo "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
            exit 1
        fi

        save_standard_outputs

        exec 1>>${LOGFILE%.log}.log
        exec 2>&1
        OUTPUTS_REDIRECTED="true"
    }

    # "private" function used by save_standard_outputs() 
    function restore_standard_outputs {
        if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
            echo "[ERROR]: ${FUNCNAME[0]}: Cannot restore standard outputs because they have NOT been redirected"
            exit 1;
        fi
        exec 1>&-   #closes FD 1 (logfile)
        exec 2>&-   #closes FD 2 (logfile)
        exec 2>&4   #restore stderr
        exec 1>&3   #restore stdout

        OUTPUTS_REDIRECTED="false"
    }

脚本内用法的示例:

echo "this goes to stdout"
redirect_outputs_to_logfile /tmp/one.log
echo "this goes to logfile"
restore_standard_outputs 
echo "this goes to stdout"

答案 12 :(得分:0)

对于tcsh,我必须使用以下命令:

command >& file

如果使用command &> file,则会出现“无效的空命令”错误。

答案 13 :(得分:0)

@ fernando-fabreti

除了您所做的之外,我还对功能做了些微改动,并删除了&-结束符,它对我来说很有效。

    function saveStandardOutputs {
      if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
        exec 3>&1
        exec 4>&2
        trap restoreStandardOutputs EXIT
      else
          echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
          exit 1;
      fi
  }

  # Params: $1 => logfile to write to
  function redirectOutputsToLogfile {
      if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
        LOGFILE=$1
        if [ -z "$LOGFILE" ]; then
            echo "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"
        fi
        if [ ! -f $LOGFILE ]; then
            touch $LOGFILE
        fi
        if [ ! -f $LOGFILE ]; then
            echo "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
            exit 1
        fi
        saveStandardOutputs
        exec 1>>${LOGFILE}
        exec 2>&1
        OUTPUTS_REDIRECTED="true"
      else
        echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
          exit 1;
      fi
  }
  function restoreStandardOutputs {
      if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
      exec 1>&3   #restore stdout
      exec 2>&4   #restore stderr
      OUTPUTS_REDIRECTED="false"
     fi
  }
  LOGFILE_NAME="tmp/one.log"
  OUTPUTS_REDIRECTED="false"

  echo "this goes to stdout"
  redirectOutputsToLogfile $LOGFILE_NAME
  echo "this goes to logfile"
  echo "${LOGFILE_NAME}"
  restoreStandardOutputs 
  echo "After restore this goes to stdout"

答案 14 :(得分:0)

在考虑使用exec 2>&1之类的情况下,我发现如果可能的话,可以使用如下bash函数重写代码,从而更易于阅读:

function myfunc(){
  [...]
}

myfunc &>mylog.log