如何使用Bash将标准输出和标准错误重定向并追加到文件中

1976

在Bash中将标准输出重定向到截断文件,我知道要使用:

cmd > file.txt

如何在Bash中将标准输出重定向到一个文件并以追加的方式写入,我知道可以使用:

cmd >> file.txt

如果想要将标准输出和标准错误一起重定向到一个截断后的文件中,我知道可以使用以下命令:

cmd &> file.txt

如何将标准输出和标准错误都重定向到一个文件中?对于我来说,cmd &>> file.txt 没有起作用。


62
请注意,&>outfile 是 Bash(和其他)特定的代码,不具备可移植性。为了实现可移植性(类似于追加答案),一直以来使用的方式是 >outfile 2>&1。 - TheBonsai
10
…而其中的排序很重要。 - Torsten Bronger
2
如果您关心两个流的内容顺序,请参考@ed-morton对类似问题的回答,此处 - Ana Nimbus
9个回答

2516
cmd >>file.txt 2>&1

Bash会按照从左到右的顺序执行重定向,具体如下:

  1. >>file.txt:以追加模式打开file.txt并将stdout重定向至该文件。
  2. 2>&1:将stderr重定向至"当前stdout所指向的位置"。在这个例子中,那就是以追加模式打开的文件。换句话说,&1 重用了当前stdout所使用的文件描述符。

40
做得很好!但是有没有办法让这个更有意义,还是我应该将其视为一个原子的bash结构? - flybywire
198
这是简单的重定向,重定向语句总是从左到右进行评估。
file:将 STDOUT 重定向到文件(追加模式)(缩写为 1>>file) 2>&1:将 STDERR 重定向到“STDOUT 所在位置”
请注意,“将 STDERR 重定向到 STDOUT”这种解释是错误的。
- TheBonsai
34
它的意思是“将输出内容(stdout,文件描述符1)追加到file.txt文件中,并将错误输出(文件描述符2)发送到与fd1相同的位置”。 - Dennis Williamson
5
然而,如果我需要将 STDERR 重定向到另一个文件并追加内容,这是否可行? - arod
47
如果你执行 cmd >>file1 2>>file2,它应该能实现你想要的效果。 - Woodrow Douglass
显示剩余4条评论

478

有两种方法可以实现这个,具体取决于你所用的Bash版本。

传统和可移植(Bash 4之前)的方法是:

cmd >> outfile 2>&1

非便携的方式,从Bash 4开始是:

cmd &>> outfile

(类似于&> outfile

为了良好的编码风格,您应该:

  • 考虑可移植性(然后使用传统方式)
  • 考虑到甚至对Bash pre-4的可移植性也是一个问题(然后使用传统方式)
  • 无论使用哪种语法,在同一脚本中不要更改它(会引起混乱!)

如果你的脚本已经以#!/bin/sh开始(无论是否有意),则Bash 4解决方案以及任何特定于Bash的代码都不适用。

还要记住,Bash 4 &>>只是更短的语法 - 它不会引入任何新的功能或类似的东西。

该语法(除其他重定向语法之外)在Bash hackers wiki中描述。


12
我更喜欢使用&>>,因为它与&>和>>保持一致。而且,“追加输出和错误到该文件”比“将错误发送到输出并将输出追加到该文件”更容易阅读。请注意,尽管Linux通常具有当前版本的bash,但在撰写本文时,OS X仍需要通过homebrew等手动安装bash 4。 - mikemaccana
1
我更喜欢它,因为它更短,每行只有两个位置,那么例如zsh会将"&>>"转换成什么? - Phillipp
2
还需要注意的是,在 cron 作业中,即使您的系统已经安装了 Bash 4,也必须使用 pre-4 语法。 - hyperknot
11
@zsero cron完全不使用bash,而是使用sh。你可以通过在crontab -e文件中添加SHELL=/bin/bash来更改默认的shell。 - Ray Foss

119
在Bash中,您还可以明确指定将重定向到不同文件的内容:
cmd >log.out 2>log_error.out

添加将是:

cmd >>log.out 2>>log_error.out

13
使用第一种选项将两个流重定向到同一个文件会导致第一个流“覆盖”第二个流,从而覆盖部分或全部内容。建议使用 cmd >> log.out 2> log.out 来代替。 - Orestis P.
7
谢谢你指出这个问题,你是对的,一个会覆盖另一个。然而,你的命令也不能正常工作。我认为唯一的写入同一文件的方法是之前提到过的 cmd >log.out 2>&1。我正在编辑我的回答以删除第一个示例。 - Aaron R.
4
cmd > my.log 2> my.log 这个命令不起作用的原因是重定向从左到右逐步执行,首先 > my.log 的作用是“创建一个新文件my.log并将标准输出重定向到该文件”,如果已存在同名文件则替换之。在此完成后,2> my.log 开始执行,它的作用也是“创建一个新文件my.log并将标准错误输出重定向到该文件”,这将覆盖掉之前创建的同名文件。由于UNIX允许删除打开的文件,stdout现在被记录到了以前叫做my.log但现在已被删除的文件中。一旦指向该文件的最后一个filehandle关闭,该文件的内容将被删除。 - Mikko Rantalainen
2
另一方面,cmd > my.log 2>&1 能够工作是因为 > my.log 表示“创建新文件 my.log 替换现有文件并将 stdout 重定向到该文件”,在这个操作完成后,2>&1 表示“将文件句柄 2 指向文件句柄 1”。根据 POSIX 规则,文件句柄 1 总是 stdout,文件句柄 2 总是 stderr,因此 stderr 然后指向第一个重定向中已经打开的文件 my.log。请注意,语法 >& 不会创建或修改实际文件,因此不需要使用 >>&。(如果 第一个 重定向是 >> my.log,那么文件将简单地以追加模式打开。) - Mikko Rantalainen

101

这应该可以正常工作:

your_command 2>&1 | tee -a file.txt

它将所有日志存储在file.txt中,并在终端中转储它们。


11
如果您也想在终端上查看输出结果,那么这就是正确的答案。然而,这并不是最初提出的问题。 - Mikko Rantalainen
使用管道的tee命令比直接重定向需要更多的时间。它可以工作,但速度较慢,内存使用更多,并且需要额外的线程。 - NeronLeVelu

75
在Bash 4中(以及Z shellzsh)4.3.11):
cmd &>> outfile

刚拿出包装盒。


4
@all:这是一个很好的答案,因为它适用于bash并且简短明了,所以我编辑过,以确保它明确提到了bash。 - mikemaccana
13
@mikemaccana:TheBonsai的回答展示了自2009年以来使用Bash 4解决方案。 - jfs
3
既然这个答案已经包含在TheBonsai的答案中,那么为什么还需要存在这个答案呢?请考虑删除它。这样你就可以获得一个“规矩徽章”(https://meta.stackexchange.com/questions/7609/what-is-the-purpose-of-the-disciplined-badge)。 - Dan Dascalescu

34

试试这个:

You_command 1> output.log  2>&1

在Bash 4中,你使用的 &> x.file 是不起作用的。对此很抱歉 :(

下面是一些额外的提示。

在Bash中,0、1、2、……、9都是文件描述符。

0代表标准输入,1代表标准输出,2代表标准错误输出。3~9是为任何其他临时用途保留的。

可以使用操作符>>>(追加)将任何文件描述符重定向到其他文件描述符或文件。

用法: <文件描述符> > <文件名 | &文件描述符>

请参见第20章 I/O重定向的参考资料。


3
你的示例与原帖所问的要求不同:它将You_command的错误输出重定向到标准输出,并将You_command的标准输出重定向到文件output.log中。此外,它不会追加到文件,而是覆盖它。 - pabouk - Ukraine stay strong
3
“文件描述符”可以是大于3的任何值,适用于所有其他文件。 - Itachi
8
您的回答展示了最常见的输出重定向错误:将 STDERR 重定向到当前 STDOUT 指向的位置,然后再将 STDOUT 重定向到文件。这不会导致 STDERR 重定向到相同的文件。重定向的顺序很重要。 - Jan Wikholm
3
这句话的意思是,我需要先将STDERROR重定向到STDOUT,然后再将STDOUT重定向到一个文件中。翻译后的命令为1 > output.log 2>&1 - Quintus.Zhou
3
是的。您的版本将错误重定向到输出并同时将输出重定向到文件中。 - Alex Yaroshevich
Re "does work in...": 你是指它在哪里“”能工作吗?(相反的意思) - Peter Mortensen

19

另一种方法:

如果使用旧版本的Bash,没有可用的&>>,您也可以这样做:

(cmd 2>&1) >> file.txt

这种方法会生成一个子shell,因此它比传统的cmd >> file.txt 2>&1方法效率更低,并且无法用于需要修改当前shell的命令(例如cdpushd),但这种方法对我来说更自然和易懂:

  1. 将标准错误输出重定向到标准输出。
  2. 将新的标准输出以追加方式重定向到文件中。

另外,使用圆括号可以消除任何顺序上的歧义,特别是如果您想将标准输出和标准错误输出管道传输到另一个命令:

为了避免启动子shell,您可以使用花括号而不是圆括号来创建一个组命令

{ cmd 2>&1; } >> file.txt

(注意,分号(或换行符)是必需的,以终止组命令。)


2
这种实现会导致系统多运行一个额外的进程。使用语法 cmd >> file 2>&1 在所有 shell 中都可以工作,而且不需要多余的进程来运行。 - Mikko Rantalainen
2
@MikkoRantalainen 我已经解释过了,它会生成一个子shell并且效率较低。这种方法的重点在于,如果效率不是很重要(而且很少有人会这么认为),这种方式更容易记忆,也更难出错。 - jamesdlin
3
我已经更新了我的答案,提供了一种避免生成子shell的变体。 - jamesdlin
如果你确实记不住语法是 cmd >> file 2>&1 还是 cmd 2>&1 >> file,我认为使用 cmd 2>&1 | cat >> file 比使用大括号或者圆括号更容易。对于我来说,一旦你理解了 cmd >> file 2>&1 的实现实际上是 "将 STDOUT 重定向到 file",然后是 "将 STDERR 重定向到当前 STDOUT 相应的 file(在第一个重定向之后显然是 file)",就立即可以明白你应该按何种顺序进行重定向了。UNIX 不支持重定向到流,只能重定向到被流指针指向的 file 描述符。 - Mikko Rantalainen

18

一些注意事项和有用的技巧

介绍

“2>&1”是什么意思?开始,我将在这个答案中使用以下命令:

ls -ld /tmp /tnt

同时填充STDINSTDERR。 (希望您的根目录中没有名为tnt条目。)

从脚本本身进行重定向

您可以计划从脚本本身进行重定向:

#!/bin/bash

exec 1>>logfile.txt
exec 2>&1

/bin/ls -ld /tmp /tnt

运行此命令将创建/追加logfile.txt文件,其中包含:
/bin/ls: cannot access '/tnt': No such file or directory
drwxrwxrwt 2 root root 4096 Apr  5 11:20 /tmp

或者

#!/bin/bash

exec 1>>logfile.txt
exec 2>>errfile.txt

/bin/ls -ld /tmp /tnt

在创建或追加标准输出logfile.txt并创建或追加错误输出errfile.txt

记录到多个不同的文件

您可以创建两个不同的日志文件,追加到一个总体日志并重新创建另一个最后日志:

#!/bin/bash

if [ -e lastlog.txt ] ;then
    mv -f lastlog.txt lastlog.old
fi
exec 1> >(tee -a overall.log /dev/tty >lastlog.txt)
exec 2>&1

ls -ld /tnt /tmp

运行此脚本将会:
- 如果 `lastlog.txt` 已经存在,将其重命名为 `lastlog.old`(如果 `lastlog.old` 已经存在,则覆盖它)。 - 创建一个新的 `lastlog.txt`。 - 将所有内容追加到 `overall.log`。 - 将所有内容输出到终端。

简单且合并的日志

#!/bin/bash

[ -e lastlog.txt ] && mv -f lastlog.txt lastlog.old
[ -e lasterr.txt ] && mv -f lasterr.txt lasterr.old

exec 1> >(tee -a overall.log combined.log /dev/tty >lastlog.txt)
exec 2> >(tee -a overall.err combined.log /dev/tty >lasterr.txt)

ls -ld /tnt /tmp

所以你有

  • lastlog.txt 上次运行的日志文件
  • lasterr.txt 上次运行的错误文件
  • lastlog.old 上一次运行的日志文件
  • lasterr.old 上一次运行的错误文件
  • overall.log 追加的总体日志文件
  • overall.err 追加的总体错误文件
  • combined.log 追加的总体错误和日志合并文件
  • 仍然输出到终端

相同,使用带时间戳的日志文件名

#!/bin/bash

sTime=${EPOCHSECONDS}
for pre in log err; do
   printf -v ${pre}file '%s-%(%Y%m%d%H%M%S)T-%08x.txt' "$pre" "$sTime" $$
done

exec 1> >(tee -a overall.log combined.log /dev/tty >"$logfile")
exec 2> >(tee -a overall.err combined.log /dev/tty >"$errfile")

ls -ld /t{nt,mp}
  • 我使用 $sTime 来确保两个文件的时间戳完全相同。(如果 $EPOCHSECONDS 被调用两次,它们之间可能会有差异!)
  • 我添加了当前进程的形成的 8 个字符的十六进制表示:currend pid$$,以确保唯一性。

第三次运行后,您必须找到 9 个文件:

ls -ltr
-rw-r--r--  1 user user   49 19 nov 10:36 log-20231119103611-00120649.txt
-rw-r--r--  1 user user   73 19 nov 10:36 err-20231119103611-00120649.txt
-rw-r--r--  1 user user   73 19 nov 10:36 err-20231119103634-001207b8.txt
-rw-r--r--  1 user user   49 19 nov 10:36 log-20231119103634-001207b8.txt
-rw-r--r--  1 user user  147 19 nov 10:40 overall.log
-rw-r--r--  1 user user  219 19 nov 10:40 overall.err
-rw-r--r--  1 user user   49 19 nov 10:40 log-20231119104000-001216f0.txt
-rw-r--r--  1 user user   73 19 nov 10:40 err-20231119104000-001216f0.txt
-rw-r--r--  1 user user  366 19 nov 10:40 combined.log

而且对于交互式会话,使用stdbuf

关于Fonic的评论,经过一些测试,我不得不同意:使用teestdbuf是无用的。 但是...

If you plan to use this in *interactive* shell, you must tell `tee` to not buffering his input/output:
# Source this to multi-log your session
[ -e lasterr.txt ] && mv -f lasterr.txt lasterr.old
[ -e lastlog.txt ] && mv -f lastlog.txt lastlog.old
exec 2> >(exec stdbuf -i0 -o0 tee -a overall.err combined.log /dev/tty >lasterr.txt)
exec 1> >(exec stdbuf -i0 -o0 tee -a overall.log combined.log /dev/tty >lastlog.txt)

Once sourced this, you could try:

ls -ld /tnt /tmp

更复杂的示例

从我的3关于如何将Unix时间戳转换为日期字符串的备注

我使用了更复杂的命令来实时解析和重组squid的日志:由于每行以毫秒为单位的UNIX EPOCH开头,我在第一个点上分割行,然后在EPOCH SECONDS之前添加@符号,将它们传递给date -f - +%F\ %T,然后使用paste -d .date的输出和剩余的行重新组合。

exec {datesfd}<> <(:)
tail -f /var/log/squid/access.log |
    tee >(
        exec sed -u 's/^\([0-9]\+\)\..*/@\1/'|
            stdbuf -o0 date -f - +%F\ %T >&$datesfd
    ) |
        sed -u 's/^[0-9]\+\.//' |
        paste -d . /dev/fd/$datesfd -

使用date命令时,需要使用stdbuf命令...
以下是关于execstdbuf命令的一些解释:
  • 使用$(...)<(...)运行forks是通过运行子shell来执行另一个子shell子子shell)来完成的。 exec命令告诉shell脚本中没有其他命令需要运行,因此二进制文件stdbuf ... tee)将作为替代进程相同级别执行(无需为运行另一个子进程保留更多内存)。

    来自bash的man页面(man -P'less +/^\ *exec\ ' bash):

        exec [-cl] [-a name] [command [arguments]]
               如果指定了命令,则替换shell。不会创建新进程....
    

    这并不是真正必要的,但可以减少系统的占用。

  • 来自stdbuf的man页面:

    NAME
           stdbuf  -  运行COMMAND,对其标准流进行修改缓冲操作。
    

    这将告诉系统对tee命令使用无缓冲I/O。因此,当有输入到来时,所有输出将立即更新


1
请参见:将输出管道到两个不同的命令,然后跟随链接到此重复问题的更详细答案中的评论。 - F. Hauri - Give Up GitHub
3
exec stdbuf 如何在这个场景中有所帮助?stdbuf 的 man 手册指出它对 tee 没有影响,能否解释一下? - Fonic
3
已发布@Fonic关于exec和stdbuf命令的一些解释! - F. Hauri - Give Up GitHub
1
谢谢,但是问题仍然存在:stdbuf 的 man 页面指出 tee 不会受到其影响,那么这有什么意义呢?引用内容如下:“注意:如果 COMMAND 调整其标准流的缓冲区(例如 'tee'),那么它将覆盖 'stdbuf' 所做出的相应更改。” - Fonic
1
@Fonic 抱歉耽搁了...我有一些测试要做...回答已经编辑好了!(你的评论被提到了) - F. Hauri - Give Up GitHub

0

这太好了!

将输出重定向到当前脚本的日志文件和标准输出。

参考https://dev59.com/eXRC5IYBdhLWcg3wYP-h#314678,非常简单和干净,它将脚本的所有输出(包括在脚本中调用的脚本)都重定向到日志文件和标准输出:

exec > >(tee -a "logs/logdata.log") 2>&1将日志打印到屏幕并写入文件中 - shriyog于2017年2月2日9:20

通常我们会将其中一个放在脚本的顶部或附近。解析命令行的脚本在解析后进行重定向。
将标准输出发送到文件
使用stderr执行>文件
exec > file exec 2>&1 将stdout和stderr都追加到文件中
exec >>文件 exec 2>&1 正如Jonathan Leffler在他的评论中提到的:
exec有两个独立的作用。第一个是用新程序替换当前正在执行的shell(脚本)。另一个是更改当前shell中的I/O重定向。这通过exec没有参数来区分。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接