在通过管道使用“ tee”时如何将stderr写入文件?


539

我知道如何使用tee写输出(STDOUT中)aaa.shbbb.out,同时还在终端显示它:

./aaa.sh | tee bbb.out

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


2
澄清一下-您是否要让stderr转到屏幕和文件?
查尔斯·达菲

我做到了,我将编辑我的帖子以澄清这一点。我相信lhunath的解决方案就足够了。谢谢大家的帮助!
jparanich

Answers:


782

我假设您仍然想在终端上看到STDERR和STDOUT。您可以寻求Josh Kelley的答案,但我发现tail在后台保留一些内容,这会使您的日志文件非常骇人且笨拙。请注意,您需要如何保留exra FD并随后通过杀死它来进行清理,从技术上讲,应在Windows XP中执行此操作trap '...' EXIT

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

只是,不仅仅是为标准输出使用它,还有为标准输出提供一个tee,为stderr提供一个。您将如何实现?进程替换和文件重定向:

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

让我们对其进行解释:

> >(..)

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

第二件事:

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

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

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

进程替换是您获得的那些非常可爱的东西之一,可以选择bash作为您的外壳而不是sh(POSIX或Bourne)作为奖励。


在中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"

5
我试过这个:$ echo "HANG" > >(tee stdout.log) 2> >(tee stderr.log >&2)可以,但是等待输入。发生这种情况的原因很简单吗?
贾斯汀

1
@SillyFreak我不明白您想做什么或遇到什么问题。回声测试 exit在stdout上不会产生任何输出,因此err将保持为空。
lhunath13年

1
谢谢你的评论;我弄清楚了我的逻辑错误是什么:当bash作为交互式shell调用时,它会打印命令提示符并回显到stderr的出口。但是,如果将stderr重定向,则bash默认情况下以非交互方式启动;比较/bin/bash 2> err/bin/bash -i 2> err
Silly Freak 2013年

15
对于那些“眼见为实”的人,可以快速测试(echo "Test Out";>&2 echo "Test Err") > >(tee stdout.log) 2> >(tee stderr.log >&2)
一下

2
。好答案:“让我们分解并解释” +1
jalanb

671

为什么不简单地:

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

这只是重定向stderrstdout,因此tee会回显日志和屏幕。也许我错过了一些东西,因为其他一些解决方案似乎真的很复杂。

注意:从bash版本4开始,您可以使用|&以下缩写2>&1 |

./aaa.sh |& tee -a log

85
如果您想将stdout(通道1)和stderr(通道2)都记录到同一文件(包含stdout和sterr的单个文件)中,那会很好。另一种更复杂的解决方案允许您将stdout和stderr分为2个不同的文件(分别为stdout.log和stderr.log)。有时那很重要,有时却不重要。
泰勒·里克

19
在许多情况下,其他解决方案比所需的复杂得多。这对我来说很完美。
dkamins 2011年

13
这种方法的问题在于,您会丢失aaa.sh进程的退出/状态代码,这可能很重要(例如,在makefile中使用时)。您接受的答案没有这个问题。
Stefaan 2013年

9
如果您不介意合并stdout / stderr,则./aaa.sh |& tee aaa.log可以使用bash。
jfs

5
@Stefaan我相信,如果在命令链前添加命令set -o pipefail;或者&&我没记错的话,您可以保留退出状态。
大卫

59

这对于通过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}

6
不,我想exec可以使用一些解释。exec >意味着将文件描述符的目标移动到某个目标。默认值为1,因此exec > /dev/null从此会话开始,将stdout的输出移至/ dev / null。可以通过查看此会话的当前文件描述符ls -l /dev/fd/。尝试一下!然后查看发出命令时发生的情况。exec 2>/tmp/stderr.log.此外,这exec 3>&1意味着创建一个编号为3的新文件描述符,并将其重定向到文件描述符1的目标。在此示例中,目标是发出命令时的屏幕。
鼓火

在屏幕和单独文件中同时显示stdout和stderr的示例非常棒!!!非常感谢!
Marcello de Sales

21

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

./aaa.sh 2> ccc.out | 开球./bbb.out

编辑:要在屏幕上同时显示stderr和stdout并将其保存到文件,可以使用bash的I / O重定向

#!/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

1
我希望用户除了文件之外,还希望stderr转到其控制台,尽管未明确指定。
查尔斯·达菲

2
我应该更清楚一些,我也确实希望将stderr显示在屏幕上。我仍然很喜欢Josh Kelley的解决方案,但是找到了更适合我需求的百叶窗。谢谢你们!
jparanich

18

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

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

另请参见如何grep标准错误流(stderr)?以及何时使用附加的文件描述符?

在bash(以及ksh和zsh)中,但在其他POSIX shell(如破折号)中则没有,可以使用进程替换

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

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


4
这似乎是唯一可以保持stdout / stderr流不变的答案(例如,不合并它们)。甜!
user1338062

最简单的方法sh,对于无法使用进程替代的cron作业有用。
罗杰杜克

13

如果使用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


5

以我为例,脚本在将stdout和stderr都重定向到文件时正在运行命令,例如:

cmd > log 2>&1

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

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

通过上述,日志将同时拥有的副本stdout,并stderr和我可以捕捉stderr从我的脚本,而不必操心stdout


4

以下内容适用于无法进行进程替换的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>&1 1>&3在我们的情况下将重定向stderrstdout并将重定向stdout到描述符的序列3。此时stderrstdout尚未合并。

实际上,stderr(as stdin)被传递到tee它登录的位置,stderr.log并且还重定向到描述符3。

描述符3一直记录着它combined.log。因此,combined.log包含stdoutstderr


好漂亮!可惜的是被低估了,因为这是一个不错的选择。我确实有KSH,顺便说一句:)
runlevel0

2

如果您使用的是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

尝试进行多次重定向时,请执行tee或隐式s cat(请参阅Redirection)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.