【问题标题】:How do I write stderr to a file while using "tee" with a pipe?如何在使用带有管道的“tee”时将 stderr 写入文件?
【发布时间】:2010-10-16 01:46:48
【问题描述】:

我知道如何使用teeaaa.sh 的输出(STDOUT)写入bbb.out,同时仍然在终端中显示:

./aaa.sh | tee bbb.out

我现在如何将STDERR 写入名为ccc.out 的文件,同时仍然显示它?

【问题讨论】:

  • 澄清一下——你想让stderr像文件一样进入屏幕吗?
  • 我做到了,我将编辑我的帖子以澄清这一点。我相信 lhunath 的解决方案就足够了。感谢大家的帮助!

标签: linux bash unix tee


【解决方案1】:

我假设您仍然希望在终端上看到 STDERR 和 STDOUT。您可以寻求 Josh Kelley 的回答,但我发现在后台保留一个 tail,它会输出您的日志文件非常笨拙和笨拙。请注意您需要如何保留一个额外的 FD,然后通过杀死它来进行清理,从技术上讲,应该在 trap '...' EXIT 中这样做。

有一种更好的方法可以做到这一点,您已经发现了它:tee

只有,而不是仅仅将它用于您的标准输出,有一个用于标准输出的三通和一个用于标准错误。你将如何做到这一点?进程替换和文件重定向:

command > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)

让我们分解并解释一下:

> >(..)

>(...)(进程替换)创建一个 FIFO 并让tee 监听它。然后,它使用>(文件重定向)将command 的STDOUT 重定向到您的第一个tee 正在侦听的FIFO。

第二个也是这样:

2> >(tee -a stderr.log >&2)

我们再次使用进程替换来创建一个tee 进程,该进程从 STDIN 读取并将其转储到 stderr.logtee 将其输入输出回 STDOUT,但由于它的输入是我们的 STDERR,我们想再次将 tee 的 STDOUT 重定向到我们的 STDERR。然后我们使用文件重定向将command的STDERR重定向到FIFO的输入(tee的STDIN)。

http://mywiki.wooledge.org/BashGuide/InputAndOutput

进程替换是您选择bash而不是sh(POSIX或Bourne)作为shell的额外好处之一。


sh 中,您必须手动执行操作:

out="${TMPDIR:-/tmp}/out.$$" err="${TMPDIR:-/tmp}/err.$$"
mkfifo "$out" "$err"
trap 'rm "$out" "$err"' EXIT
tee -a stdout.log < "$out" &
tee -a stderr.log < "$err" >&2 &
command >"$out" 2>"$err"

【讨论】:

  • 我试过这个:$ echo "HANG" &gt; &gt;(tee stdout.log) 2&gt; &gt;(tee stderr.log &gt;&amp;2) 有效,但等待输入。发生这种情况有一个简单的原因吗?
  • @SillyFreak 我不明白你想做什么或者你有什么问题。回声测试; exit 不会在 stdout 上产生任何输出,因此 err 将保持为空。
  • 感谢您的评论;后来我弄清楚了我的逻辑错误是什么:当作为交互式 shell 调用时,bash 会打印一个命令提示符并将 exit 回显到 stderr。但是,如果 stderr 被重定向,bash 默认以非交互方式启动;比较/bin/bash 2&gt; err/bin/bash -i 2&gt; err
  • 对于那些“眼见为实”的人,快速测试一下:(echo "Test Out";&gt;&amp;2 echo "Test Err") &gt; &gt;(tee stdout.log) 2&gt; &gt;(tee stderr.log &gt;&amp;2)
  • 这个可以用来将两者写入同一个文件,但是以同步的方式,这样stderr和stdout消息的顺序被保留了,而supbrocess的每个流仍然被定向到对应的流调用过程?我不知道,但我可以想象如果 tee 进行一些缓冲,子进程可能会完成向 stdout 写入一些内容并写入 stderr,但是接收到 stderr 消息的第二个 tee 可能会比第一个更早完成将其写入文件。
【解决方案2】:

为什么不简单:

./aaa.sh 2>&1 | tee -a log

这只是将stderr 重定向到stdout,因此 tee 回显到日志和屏幕。也许我遗漏了一些东西,因为其他一些解决方案看起来真的很复杂。

注意:从 bash 版本 4 开始,您可以使用 |&amp; 作为 2&gt;&amp;1 | 的缩写:

./aaa.sh |& tee -a log

【讨论】:

  • 如果您希望 stdout(通道 1)和 stderr(通道 2)都记录到同一个文件(包含 stdout 和 sterr 的混合的单个文件),那效果很好。另一种更复杂的解决方案允许您将 stdout 和 stderr 分成 2 个不同的文件(分别为 stdout.log 和 stderr.log)。有时这很重要,有时则不重要。
  • 其他解决方案在许多情况下远比必要的复杂。这个非常适合我。
  • 这种方法的问题是您丢失了 aaa.sh 进程的退出/状态代码,这可能很重要(例如,在 makefile 中使用时)。接受的答案没有这个问题。
  • 如果您不介意合并标准输出/标准错误,那么 ./aaa.sh |&amp; tee aaa.log 可以工作(在 bash 中)。
【解决方案3】:

这对于通过 google 找到此内容的人可能很有用。只需取消注释您想尝试的示例。当然,您可以随意重命名输出文件。

#!/bin/bash

STATUSFILE=x.out
LOGFILE=x.log

### All output to screen
### Do nothing, this is the default


### All Output to one file, nothing to the screen
#exec > ${LOGFILE} 2>&1


### All output to one file and all output to the screen
#exec > >(tee ${LOGFILE}) 2>&1


### All output to one file, STDOUT to the screen
#exec > >(tee -a ${LOGFILE}) 2> >(tee -a ${LOGFILE} >/dev/null)


### All output to one file, STDERR to the screen
### Note you need both of these lines for this to work
#exec 3>&1
#exec > >(tee -a ${LOGFILE} >/dev/null) 2> >(tee -a ${LOGFILE} >&3)


### STDOUT to STATUSFILE, stderr to LOGFILE, nothing to the screen
#exec > ${STATUSFILE} 2>${LOGFILE}


### STDOUT to STATUSFILE, stderr to LOGFILE and all output to the screen
#exec > >(tee ${STATUSFILE}) 2> >(tee ${LOGFILE} >&2)


### STDOUT to STATUSFILE and screen, STDERR to LOGFILE
#exec > >(tee ${STATUSFILE}) 2>${LOGFILE}


### STDOUT to STATUSFILE, STDERR to LOGFILE and screen
#exec > ${STATUSFILE} 2> >(tee ${LOGFILE} >&2)


echo "This is a test"
ls -l sdgshgswogswghthb_this_file_will_not_exist_so_we_get_output_to_stderr_aronkjegralhfaff
ls -l ${0}

【讨论】:

  • 不,我猜 exec 可以使用一些解释。 exec &gt; 表示,将文件描述符的目标移动到某个目的地。默认值为 1,因此,exec &gt; /dev/null 从现在开始在此会话中将 stdout 的输出移动到 /dev/null。通过执行ls -l /dev/fd/ 可以查看此会话的当前文件描述符。试试吧!然后看看当你发出exec 2&gt;/tmp/stderr.log. 时会发生什么另外,exec 3&gt;&amp;1 意味着,创建一个编号为 3 的新文件描述符,并将其重定向到文件描述符 1 的目标。在示例中,目标是命令执行时的屏幕已发行。
  • 在屏幕和单独文件中显示 stdout 和 stderr 的示例太棒了!非常感谢!
  • “所有输出到一个文件,STDERR 到屏幕”部分可以放在一行(只需将3&gt;&amp;1 放在前面):exec 3&gt;&amp;1 &gt; &gt;(tee -a ${LOGFILE} &gt;/dev/null) 2&gt; &gt;(tee -a ${LOGFILE} &gt;&amp;3) 如果您希望 stderr 打印到,请注意屏幕仍然为 2,只需切换到 3&gt;&amp;2 而不是 3&gt;&amp;1
  • 如果您不想在屏幕上显示输出,也不需要tee 命令。您可以直接输出到文件,而不是使用带有 /dev/null 的 tee:只需执行 exec 3&gt;&amp;2 1&gt;${LOGFILE} 2&gt; &gt;(tee -a ${LOGFILE} &gt;&amp;3)
【解决方案4】:

要将 stderr 重定向到文件,将 stdout 显示到屏幕,并将 stdout 保存到文件:

./aaa.sh 2>ccc.out | tee ./bbb.out

编辑:要将 stderr 和 stdout 都显示到屏幕并将两者保存到文件中,您可以使用 bash 的 I/O redirection

#!/bin/bash

# Create a new file descriptor 4, pointed at the file
# which will receive stderr.
exec 4<>ccc.out

# Also print the contents of this file to screen.
tail -f ccc.out &

# Run the command; tee stdout as normal, and send stderr
# to our file descriptor 4.
./aaa.sh 2>&4 | tee bbb.out

# Clean up: Close file descriptor 4 and kill tail -f.
exec 4>&-
kill %1

【讨论】:

  • 我希望用户除了文件之外还希望 stderr 转到他们的控制台,尽管没有明确指定。
  • 我应该更清楚,我也确实希望 stderr 出现在屏幕上。我仍然喜欢 Josh Kelley 的解决方案,但发现 lhunath 更适合我的需要。谢谢大家!
【解决方案5】:

换句话说,您希望将 stdout 输送到一个过滤器 (tee bbb.out) 并将 stderr 输送到另一个过滤器 (tee ccc.out)。除了标准输出之外,没有标准的方法可以将任何内容通过管道传输到另一个命令中,但是您可以通过处理文件描述符来解决这个问题。

{ { ./aaa.sh | tee bbb.out; } 2>&1 1>&3 | tee ccc.out; } 3>&1 1>&2

另请参阅How to grep standard error stream (stderr)?When would you use an additional file descriptor?

在 bash(以及 ksh 和 zsh)中,但不能在 dash 等其他 POSIX shell 中,您可以使用process substitution

./aaa.sh > >(tee bbb.out) 2> >(tee ccc.out)

请注意,在 bash 中,此命令会在 ./aaa.sh 完成后立即返回,即使 tee 命令仍在执行(ksh 和 zsh 会等待子进程)。如果您执行./aaa.sh &gt; &gt;(tee bbb.out) 2&gt; &gt;(tee ccc.out); process_logs bbb.out ccc.out 之类的操作,这可能是个问题。在这种情况下,请改用文件描述符杂耍或 ksh/zsh。

【讨论】:

  • 这似乎是允许保持 stdout/stderr 流原样(例如,不合并它们)的唯一答案。甜!
  • sh 的最简单方法,适用于无法使用进程替换的 cron 作业。
【解决方案6】:

如果使用 bash:

# Redirect standard out and standard error separately
% cmd >stdout-redirect 2>stderr-redirect

# Redirect standard error and out together
% cmd >stdout-redirect 2>&1

# Merge standard error with standard out and pipe
% cmd 2>&1 |cmd2

信用(不是从我的头顶回答)在这里:http://www.cygwin.com/ml/cygwin/2003-06/msg00772.html

【讨论】:

    【解决方案7】:

    如果你使用zsh,你可以使用多个重定向,所以你甚至不需要tee

    ./cmd 1>&1 2>&2 1>out_file 2>err_file
    

    在这里,您只是将每个流重定向到自身目标文件。


    完整示例

    % (echo "out"; echo "err">/dev/stderr) 1>&1 2>&2 1>/tmp/out_file 2>/tmp/err_file
    out
    err
    % cat /tmp/out_file
    out
    % cat /tmp/err_file
    err
    

    请注意,这需要设置 MULTIOS 选项(这是默认设置)。

    MULTIOS

    在尝试多次重定向时执行隐式tees 或cats(请参阅Redirection)。

    【讨论】:

      【解决方案8】:

      在我的例子中,一个脚本正在运行命令,同时将 stdout 和 stderr 都重定向到一个文件,类似于:

      cmd > log 2>&1
      

      我需要对其进行更新,以便在出现故障时根据错误消息采取一些措施。我当然可以删除 dup 2&gt;&amp;1 并从脚本中捕获 stderr,但是错误消息不会进入日志文件以供参考。虽然@lhunath 接受的答案应该做同样的事情,但它将stdoutstderr 重定向到不同的文件,这不是我想要的,但它帮助我想出了我需要的确切解决方案:

      (cmd 2> >(tee /dev/stderr)) > log
      

      通过以上内容,日志将包含stdoutstderr 的副本,我可以从我的脚本中捕获stderr,而不必担心stdout

      【讨论】:

        【解决方案9】:

        以下内容适用于进程替换不可用的 KornShell(ksh),

        # create a combined(stdin and stdout) collector
        exec 3 <> combined.log
        
        # stream stderr instead of stdout to tee, while draining all stdout to the collector
        ./aaa.sh 2>&1 1>&3 | tee -a stderr.log 1>&3
        
        # cleanup collector
        exec 3>&-
        

        这里真正的技巧是2&gt;&amp;1 1&gt;&amp;3 的序列,在我们的例子中它将stderr 重定向到stdout 并将stdout 重定向到描述符3。此时stderrstdout 尚未合并。

        实际上,stderr(as stdin) 被传递到 tee,它记录到 stderr.log 并重定向到描述符 3。

        描述符3 一直将其记录到combined.log。所以combined.log 包含stdoutstderr

        【讨论】:

        • 漂亮!可惜被低估了,因为它是一个不错的选择。我确实有 KSH,顺便说一句:)
        【解决方案10】:

        accepted answer well explained by lhunath一样可以使用

        command > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)
        

        请注意,如果您使用 bash,可能会遇到一些问题

        让我以matthew-wilcoxson 为例。

        对于那些“眼见为实”的人来说,快速测试一下:

        (echo "Test Out";>&2 echo "Test Err") > >(tee stdout.log) 2> >(tee stderr.log >&2)
        

        就我个人而言,当我尝试时,我得到了这样的结果:

        user@computer:~$ (echo "Test Out";>&2 echo "Test Err") > >(tee stdout.log) 2> >(tee stderr.log >&2)
        user@computer:~$ Test Out
        Test Err
        
        

        两条消息没有出现在同一级别。为什么Test Out 好像是我之前的命令?
        提示在一个空白行,让我认为过程没有完成,当我按Enter这个修复它。
        当我检查文件的内容时,没关系,重定向有效。

        让我们再做一次测试。

        function outerr() {
          echo "out"     # stdout
          echo >&2 "err" # stderr
        }
        
        user@computer:~$ outerr
        out
        err
        
        user@computer:~$ outerr >/dev/null
        err
        
        user@computer:~$ outerr 2>/dev/null
        out
        

        再次尝试重定向,但使用此功能。

        function test_redirect() {
          fout="stdout.log"
          ferr="stderr.log"
          echo "$ outerr"
          (outerr) > >(tee "$fout") 2> >(tee "$ferr" >&2)
          echo "# $fout content :"
          cat "$fout"
          echo "# $ferr content :"
          cat "$ferr"
        }
        

        就我个人而言,我有这样的结果:

        user@computer:~$ test_redirect
        $ outerr
        # stdout.log content :
        out
        out
        err
        # stderr.log content :
        err
        user@computer:~$
        

        空行没有提示,但是看不到正常输出,stdout.log内容好像有错,只有stderr.log好像没问题。 如果我重新启动它,输出可能会有所不同...

        那么,为什么?

        因为,like explained here

        请注意,在 bash 中,此命令会在 [first command] 完成后立即返回,即使 tee 命令仍在执行(ksh 和 zsh 确实会等待子进程)

        所以,如果你使用 bash,最好使用this other answer 中给出的更好的例子:

        { { outerr | tee "$fout"; } 2>&1 1>&3 | tee "$ferr"; } 3>&1 1>&2
        

        它将解决以前的问题。

        现在的问题是,如何检索退出状态码?
        $? 不起作用。
        我没有找到比使用set -o pipefailset +o pipefail 关闭)打开管道故障并像这样使用${PIPESTATUS[0]} 更好的解决方案

        function outerr() {
          echo "out"
          echo >&2 "err"
          return 11
        }
        
        function test_outerr() {
          local - # To preserve set option
          ! [[ -o pipefail ]] && set -o pipefail; # Or use second part directly
          local fout="stdout.log"
          local ferr="stderr.log"
          echo "$ outerr"
          { { outerr | tee "$fout"; } 2>&1 1>&3 | tee "$ferr"; } 3>&1 1>&2
          # First save the status or it will be lost
          local status="${PIPESTATUS[0]}" # Save first, the second is 0, perhaps tee status code.
          echo "==="
          echo "# $fout content :"
          echo "<==="
          cat "$fout"
          echo "===>"
          echo "# $ferr content :"
          echo "<==="
          cat "$ferr"
          echo "===>"
          if (( status > 0 )); then
            echo "Fail $status > 0"
            return "$status" # or whatever
          fi
        }
        
        user@computer:~$ test_outerr
        $ outerr
        err
        out
        ===
        # stdout.log content :
        <===
        out
        ===>
        # stderr.log content :
        <===
        err
        ===>
        Fail 11 > 0
        

        【讨论】:

          【解决方案11】:

          发送到标准错误 (STDERR) 的编译错误可以通过以下方式重定向或保存到文件中:

          重击: $ gcc temp.c &> error.log

          csh: % gcc temp.c |& tee error.log

          见:https://www.linuxquestions.org/questions/linux-kernel-70/how-to-redirect-compilation-build-error-to-a-file-831099/

          【讨论】:

            【解决方案12】:

            感谢@lhunath 在 posix 中的回答, 这是我在 posix 中需要正确修复的更复杂的情况:

            # Start script main() function
            # - We redirect stdout to file_out AND terminal
            # - We redirect stderr to file_err, file_out AND terminal
            # - Terminal and file_out have both stdout and stderr, while file_err only holds stderr
            
            main() {
                # my main function
            }
            
            log_path="/my_temp_dir"
            pfout_fifo="${log_path:-/tmp}/pfout_fifo.$$"
            pferr_fifo="${log_path:-/tmp}/pferr_fifo.$$"
            
            mkfifo "$pfout_fifo" "$pferr_fifo"
            trap 'rm "$pfout_fifo" "$pferr_fifo"' EXIT
            
            tee -a "file_out" < "$pfout_fifo" &
                tee -a "file_err" < "$pferr_fifo" >>"$pfout_fifo" &
                main "$@" >"$pfout_fifo" 2>"$pferr_fifo"; exit
            

            【讨论】:

              猜你喜欢
              • 2011-01-18
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2017-04-10
              • 1970-01-01
              • 2017-07-04
              • 1970-01-01
              相关资源
              最近更新 更多