在 Bash 中重定向标准错误和标准输出 [重复]

Posted

技术标签:

【中文标题】在 Bash 中重定向标准错误和标准输出 [重复]【英文标题】:Redirect stderr and stdout in Bash [duplicate] 【发布时间】:2010-10-12 21:02:06 【问题描述】:

我想将一个进程的standard output 和standard error 都重定向到一个文件。我如何在 Bash 中做到这一点?

【问题讨论】:

我想说这是一个非常有用的问题。许多人不知道如何执行此操作,因为他们不必经常这样做,而且这不是 Bash 的最佳记录行为。 有时查看输出(像往常一样)并将其重定向到文件很有用。请参阅下面 Marko 的答案。 (我之所以在这里这么说,是因为如果第一个接受的答案足以解决问题,那么很容易查看它,但其他答案通常会提供有用的信息。) 【参考方案1】:

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

这是我找到的解决方案:

command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
首先交换标准错误和标准输出 然后将标准输出附加到日志文件中 管道标准错误到 tee 并将其也附加到日志文件中

【讨论】:

顺便说一句,这对我不起作用(日志文件为空)。 |tee 没有效果。相反,我使用***.com/questions/692000/… 让它工作【参考方案2】:

在您考虑使用 exec 2>&1 之类的东西的情况下,我发现如果可能的话,使用这样的 Bash 函数重写代码更容易阅读:

function myfunc()
  [...]


myfunc &>mylog.log

【讨论】:

【参考方案3】:
# Close standard output file descriptor
exec 1<&-
# Close standard error file descriptor
exec 2<&-

# Open standard output as $LOG_FILE file for read and write.
exec 1<>$LOG_FILE

# Redirect standard error to standard output
exec 2>&1

echo "This line will appear in $LOG_FILE, not 'on screen'"

现在,一个简单的回显将写入 $LOG_FILE,它对于守护进程很有用。

致原帖作者,

这取决于您需要实现什么。如果您只需要重定向输入/输出从脚本调用的命令,那么答案已经给出。我的是关于在提到的代码 sn-p 之后重定向 inin 当前脚本,该脚本会影响所有命令/内置(包括分叉)。


另一个很酷的解决方案是同时重定向到标准错误和标准输出以一次记录到日志文件,这涉及将“流”分成两个。此功能由 'tee' 命令提供,该命令可以一次写入/附加到多个文件描述符(文件、套接字、管道等):tee FILE1 FILE2 ... &gt;(cmd1) &gt;(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

所以,从一开始。假设我们有一个终端连接到/dev/stdout(文件描述符#1)和/dev/stderr(文件描述符#2)。实际上,它可以是管道、套接字或其他任何东西。

创建file descriptors (FD) #3 和 #4 并分别指向与 #1 和 #2 相同的“位置”。从现在开始,更改文件描述符 #1 不会影响文件描述符 #3。现在,文件描述符#3 和#4 分别指向标准输出和标准错误。这些将用作真正的终端标准输出和标准错误。 1> >(...) 将标准输出重定向到括号中的命令 括号(子shell)执行'tee',从exec的标准输出(管道)读取并通过另一个管道重定向到'logger'命令到括号中的子shell。同时它将相同的输入复制到文件描述符#3(终端) 第二部分非常相似,是关于对标准错误和文件描述符 #2 和 #4 执行相同的技巧。

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

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

...如下:

$ ./my_script
Will end up in standard output (terminal) and /var/log/messages

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

如果你想看到更清晰的图片,请将这两行添加到脚本中:

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

【讨论】:

只有一个例外。在您编写的第一个示例中: exec 1$LOG_FILE 。它导致原始日志文件总是被覆盖。对于真正的登录,更好的方法是: exec 1>>$LOG_FILE 它导致日志总是被附加。 确实如此,尽管这取决于意图。我的方法是始终创建一个唯一且带有时间戳的日志文件。另一种是追加。两种方式都是'logrotateable'。我更喜欢需要较少解析的单独文件,但正如我所说,无论什么让你的船漂浮:) 您的第二个解决方案提供了丰富的信息,但是所有清理代码是怎么回事?它似乎不相关,如果是这样,只会混淆一个很好的例子。我还希望看到它稍作修改,以便 FD 1 和 2 不会重定向到记录器,而是 3 和 4 以便任何调用此脚本的东西都可以在 stdout==1 的共同假设下进一步操纵 1 和 2和 stderr==2,但我的简短实验表明这更复杂。 我更喜欢清理代码。这可能会分散核心示例的注意力,但剥离它会使示例不完整。网络已经充满了没有错误处理的示例,或者至少友好地说明它仍然需要大约一百行代码才能安全使用。 我想详细说明清理代码。它是脚本的一部分,它使 ergo 对 HANG-UP 信号免疫。 'tee' 和 'logger' 是由相同 PPID 产生的进程,它们从主 bash 脚本继承 HUP 陷阱。因此,一旦主进程死亡,它们就会被 init[1] 继承。他们不会变成僵尸(defunc)。如果主脚本终止,清理代码确保所有后台任务都被终止。它也适用于可能已在后台创建和运行的任何其他进程。【参考方案4】:

“最简单”的方式(仅限 Bash 4):

ls * 2>&- 1>&-

【讨论】:

【参考方案5】:

对于需要“管道”的情况,可以使用|&amp;

例如:

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 的解决方案可以分别管道标准输出和标准错误(从“sort -c”的标准错误,或从“sort -h”的标准错误)。

【讨论】:

这实际上非常重要,而且鲜为人知。好决定。您可能还想解释&amp; 与管道结合使用时的作用。【参考方案6】:

除了 Fernando Fabreti did 之外,我稍微更改了功能并删除了 &amp;- 关闭,它对我有用。

    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
  

  # Parameters: $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 standard output"
  redirectOutputsToLogfile $LOGFILE_NAME
  echo "this goes to logfile"
  echo "$LOGFILE_NAME"
  restoreStandardOutputs
  echo "After restore this goes to standard output"

【讨论】:

【参考方案7】:

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

command >& file

如果使用command &amp;&gt; file,会报“Invalid null command”错误。

【讨论】:

【参考方案8】:
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" )

相关:将标准输出和标准错误写入syslog。

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

【讨论】:

我猜它不起作用,因为“/dev/fd/3 Permission denied”。更改为 >&3 可能会有所帮助。【参考方案9】:

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

some_command >file.log 2>&1

Chapter 20. I/O Redirection

这种格式优于仅适用于 Bash 的最流行的 &amp;&gt; 格式。在 Bourne shell 中,它可以被解释为在后台运行命令。格式也更易读 - 2(是标准错误)重定向到 1(标准输出)。

【讨论】:

这种方法比 some_command &> file.log 有什么优势? 如果你想附加到一个文件,那么你必须这样做: echo "foo" 2>&1 1>> bar.txt AFAIK 没有办法使用 &> 附加 啊,对不起,回显“foo”1>> bar.txt 2>&1 我认为 2>&1 将 stderr 重定向到 stdout 的解释是错误的;我相信更准确的说法是,它将 stderr 发送到 stdout 目前正在发送的同一个地方。因此将 2>&1 放在 之后,第一个重定向是必不可少的。 @SlappyTheFish,其实 一种方式:"&>>" From bash man:" 附加标准输出和标准错误的格式是:&>>word这在语义上等价于 >>word 2>&1 "【参考方案10】:
bash your_script.sh 1>file.log 2>&1

1&gt;file.log 指示 shell 将标准输出发送到文件 file.log2&gt;&amp;1 告诉它将标准错误(文件描述符 2)重定向到标准输出(文件描述符 1)。

注意:正如 liw.fi 指出的那样,顺序很重要,2&gt;&amp;1 1&gt;file.log 不起作用。

【讨论】:

【参考方案11】:
do_something 2>&1 | tee -a some_file

这会将标准错误重定向到标准输出并将标准输出重定向到some_file将其打印到标准输出。

【讨论】:

在 AIX (ksh) 上,您的解决方案有效。接受的答案do_something &amp;&gt;filename 没有。 +1。 @Daniel,但这个问题专门针对 bash 我收到Ambiguous output redirect. 知道为什么吗? 我有一个 ruby​​ 脚本(我不想以任何方式修改它)以粗体红色打印错误消息。然后从我的 bash 脚本(我可以修改)调用这个 ruby​​ 脚本。当我使用上述内容时,它会以纯文本形式打印错误消息,减去格式。有什么方法可以保留屏幕格式并在文件中获取输出(stdout 和 stderr)? 请注意(默认情况下)这有一个副作用,$? 不再指代do_something 的退出状态,而是tee 的退出状态。【参考方案12】:

看看here。应该是:

yourcommand &> filename

它将标准输出和标准错误重定向到文件filename

【讨论】:

根据Bash Hackers Wiki,不推荐使用此语法。是吗? 根据wiki.bash-hackers.org/scripting/obsolete,它似乎已经过时,因为它不是 POSIX 的一部分,但是 bash 手册页没有提到它会在不久的将来从 bash 中删除。手册页确实指定了 '&>' 而不是 '>&' 的首选项,否则它是等效的。 我想我们不应该使用 &>,因为它不在 POSIX 中,并且像“dash”这样的常见 shell 不支持它。 额外提示:如果您在脚本中使用它,请确保它以 #!/bin/bash 而不是 #!/bin/sh 开头,因为 in 需要 bash。 或 &>> 追加而不是覆盖。【参考方案13】:

以下函数可用于自动切换输出 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"

【讨论】:

当我使用你的函数并尝试恢复标准输出时,我得到 echo: write error: Bad file number 重定向工作正常...恢复似乎没有 为了让您的脚本正常工作,我不得不注释掉这些行并更改了顺序:#exec 1>&- #closes FD 1 (logfile) #exec 2>&- #关闭FD 2(日志文件); exec 1>&3 #restore stdout exec 2>&4 #restore stderr 很抱歉听到这个消息。在 CentOS 7、bash 4.2.46 中运行时,我没有收到任何错误。我已经注释了获得这些命令的参考。它是:参考:logan.tw/posts/2016/02/20/open-and-close-files-in-bash 我正在 AIX 上运行这些命令,这可能就是原因。我为我所做的修复添加了一个帖子。【参考方案14】:

简答:Command &gt;filename 2&gt;&amp;1Command &amp;&gt;filename


解释:

考虑以下代码,它将单词“stdout”打印到stdout,并将单词“stderror”打印到stderror。

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

请注意,'&' 运算符告诉 bash 2 是文件描述符(指向 stderr)而不是文件名。如果我们省略了 '&',此命令会将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 首先看到 &gt;/dev/null(与 1&gt;/dev/null 相同),并将文件描述符 1 设置为指向 /dev/null 而不是 stdout。完成此操作后,Bash 向右移动并看到2&gt;&amp;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

标准错误

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


最后,我注意到有一种更简单的方法可以做到这一点:

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

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

要点:

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

最后看看这些很棒的资源:

Bash Documentation on Redirection、An Explanation of File Descriptor Tables、Introduction to Pointers

【讨论】:

文件描述符 (0, 1, 2) 只是表中的偏移量。当使用 2>&1 时,效果是插槽 FD[2] = dup(1) 所以 FD[1] 指向的地方 FD[2] 现在指向。当您将 FD[1] 更改为指向 /dev/null 时,FD[1] 会更改,但不会更改 FD[2] 插槽(指向标准输出)。我使用术语 dup() 因为这是用于复制文件描述符的系统调用。【参考方案15】:

奇怪的是,这行得通:

yourcommand &> filename

但这会产生语法错误:

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

你必须使用:

yourcommand 1>> filename 2>&1

【讨论】:

&amp;&gt;&gt; 似乎适用于 BASH 4:$ echo $BASH_VERSION 4.1.5(1)-release $ (echo to stdout; echo to stderr &gt; /dev/stderr) &amp;&gt;&gt; /dev/null

以上是关于在 Bash 中重定向标准错误和标准输出 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

linux中重定向学习总结

Linux中重定向管道和grep命令总结

Linux中重定向管道和grep命令总结

Linux中重定向管道和grep命令总结

Linux中重定向应注意的事情

将所有输出重定向到 Bash 中的文件 [重复]