ChatGPT解决这个技术问题 Extra ChatGPT

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

要将 standard output 重定向到 Bash 中的截断文件,我知道要使用:

cmd > file.txt

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

cmd >> file.txt

要将标准输出和 standard error 都重定向到截断的文件,我知道要使用:

cmd &> file.txt

如何重定向附加到文件的标准输出和标准错误? cmd &>> file.txt 对我不起作用。

我想指出 &>outfile 是 Bash(和其他)特定代码,不可移植。便携的方式(类似于附加的答案)一直是并且仍然是 >outfile 2>&1
……排序很重要。
这回答了你的问题了吗? Redirect stderr and stdout in Bash
@BrettHale 我将其标记为该问题的副本,主要是因为此处接受的答案可移植到其他外壳,并且可以更好地表达该问题。奇怪的是,同一个用户两次问了同一个问题,直到现在才被注意到。

F
Fritz
cmd >>file.txt 2>&1

Bash 从左到右执行重定向,如下所示:

>>file.txt:以追加模式打开 file.txt 并将标准输出重定向到那里。 2>&1:将 stderr 重定向到“stdout 当前所在的位置”。在这种情况下,这是一个以附加模式打开的文件。换句话说,&1 重用了 stdout 当前使用的文件描述符。


效果很好!但是有没有办法理解这一点,或者我应该把它当作一个原子 bash 构造吗?
这是简单的重定向,重定向语句的评估一如既往,从左到右。 >>文件:红色。 STDOUT 到文件(附加模式)(1>>file 的缩写)2>&1:红色。 STDERR 到“stdout 去哪里” 请注意,解释“将 STDERR 重定向到 STDOUT”是错误的。
它说“将输出(stdout,文件描述符1)附加到file.txt并将stderr(文件描述符2)发送到与fd1相同的位置”。
@TheBonsai 但是,如果我需要将 STDERR 重定向到另一个文件但要追加怎么办?这可能吗?
如果你这样做 cmd >>file1 2>>file2 它应该达到你想要的。
M
Matthias Braun

有两种方法可以做到这一点,具体取决于您的 Bash 版本。

经典且便携(Bash pre-4)的方式是:

cmd >> outfile 2>&1

从 Bash 4 开始的一种不可移植的方式是

cmd &>> outfile

(类似于 &> outfile

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

确定是否需要考虑可移植性(然后使用经典方式)

决定是否考虑到 Bash pre-4 的可移植性(然后使用经典方式)

无论您使用哪种语法,都不要在同一个脚本中更改它(混淆!)

如果您的脚本已经以 #!/bin/sh 开头(无论是否有意),那么 Bash 4 解决方案以及通常任何特定于 Bash 的代码都不是可行的方法。

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

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


我更喜欢 &>>,因为它与 &> 和 >> 一致。阅读“将输出和错误附加到此文件”也比“将错误发送到输出,将输出附加到此文件”更容易。请注意,虽然 Linux 通常具有当前版本的 bash,但 OS X 在撰写本文时仍需要通过自制软件等手动安装 bash 4。
我更喜欢它,因为它更短,每行只有两个位置,那么例如 zsh 会用“&>>”做什么?
同样重要的是要注意,在 cron 作业中,您必须使用 pre-4 语法,即使您的系统有 Bash 4。
@zsero cron 根本不使用 bash ......它使用 sh。您可以通过将 SHELL=/bin/bash 添加到 crontab -e 文件来更改默认 shell。
A
Aaron R.

在 Bash 中,您还可以明确指定重定向到不同的文件:

cmd >log.out 2>log_error.out

附加将是:

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

使用第一个选项将两个流重定向到同一个文件将导致第一个流在第二个的“顶部”写入,覆盖部分或全部内容。使用 cmd >> log.out 2> log.out 代替。
感谢您了解这一点;你是对的,一个会破坏另一个。但是,您的命令也不起作用。我认为写入同一文件的唯一方法是在 cmd >log.out 2>&1 之前给出。我正在编辑我的答案以删除第一个示例。
cmd > my.log 2> my.log 不起作用的原因是重定向是从左到右评估的,并且 > my.log 说“创建新文件 my.log 替换现有文件并将 stdout 重定向到该文件”和 之后 已经完成,评估 2> my.log 并显示“创建新文件 my.log 替换现有文件并将 stderr 重定向到该文件”。由于 UNIX 允许删除打开的文件,stdout 现在记录到以前称为 my.log 但已被删除的文件中。一旦该文件的最后一个文件句柄关闭,文件 contents 也将被删除。
另一方面,cmd > my.log 2>&1 有效,因为 > my.log 说“创建新文件 my.log 替换现有文件并将 stdout 重定向到该文件”,然后已经完成,2>&1 说“点文件句柄2 到文件句柄 1"。并且根据 POSIX 规则,文件句柄 1 始终是标准输出,2 始终是标准错误,因此 stderr 然后从第一次重定向指向已经打开的文件 my.log。请注意,语法 >& 不会创建或修改实际文件,因此不需要 >>&。 (如果 first 重定向为 >> my.log,则文件只是以附加模式打开。)
P
Peter Mortensen

这应该可以正常工作:

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

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


如果您也想在终端中查看输出,这也是正确的答案。然而,这并不是最初提出的问题。
P
Peter Mortensen

在 Bash 4(以及 Z shell (zsh) 4.3.11)中:

cmd &>> outfile

开箱即用。


@all:这是一个很好的答案,因为它适用于 bash 并且很简短,所以我进行了编辑以确保它明确提到 bash。
@mikemaccana:TheBonsai's answer 显示自 2009 年以来的 bash 4 解决方案
为什么这个答案甚至存在于 TheBonsai 的答案中?请考虑删除它。您将获得一个 disciplined badge
P
Peter Mortensen

尝试这个:

You_command 1> output.log  2>&1

您对 &> x.file 的使用在 Bash 4 中确实有效。抱歉:(

这里有一些额外的提示。

0, 1, 2, ..., 9 是 bash 中的文件描述符。

0 代表standard input,1 代表standard output,2 代表standard error。 3~9 留作其他临时用途。

任何文件描述符都可以通过使用运算符 >>>(append) 重定向到其他文件描述符或文件。

用法: >

请参阅 Chapter 20. I/O Redirection 中的参考资料。


您的示例将执行与 OP 所要求的不同的操作:它将 You_command 的 stderr 重定向到 stdout,将 You_command 的 stdout 重定向到文件 output.log。此外,它不会附加到文件,但会覆盖它。
正确:对于所有其他文件,文件描述符可以是大于 3 的任何值。
您的答案显示了最常见的输出重定向错误:将 STDERR 重定向到 STDOUT 当前指向的位置,并且仅在将 STDOUT 重定向到文件之后。这不会导致 STDERR 被重定向到同一个文件。重定向的顺序很重要。
这是否意味着,我应该首先将 STDERROR 重定向到 STDOUT,然后将 STDOUT 重定向到文件。 1 > output.log 2>&1
@Quintus.Zhou Yup。您的版本将 err 重定向到 out,同时将 out 重定向到文件。
P
Peter Mortensen

另一种方法:

如果使用 &>> 不可用的旧版本 Bash,您还可以执行以下操作:

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

这会生成一个子 shell,因此它的效率低于传统的 cmd >> file.txt 2>&1 方法,因此它不适用于需要修改当前 shell 的命令(例如 cdpushd),但这种方法感觉更对我来说自然且可以理解:

将标准错误重定向到标准输出。通过附加到文件来重定向新的标准输出。

此外,括号消除了任何顺序的歧义,特别是如果您想将标准输出和标准错误传递给另一个命令。

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

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

(请注意,终止 group 命令需要分号(或换行符)。)


此实现会导致系统运行一个额外的进程。使用语法 cmd >> file 2>&1 可以在所有 shell 中使用,并且不需要额外的进程来运行。
@MikkoRantalainen 我已经解释过它会产生一个子shell并且效率较低。这种方法的重点是,如果效率不是什么大问题(而且很少如此),那么这种方式更容易记住,也更难出错。
@MikkoRantalainen 我已经用一个避免产生子外壳的变体更新了我的答案。
如果您真的不记得语法是 cmd >> file 2>&1 还是 cmd 2>&1 >> file,我认为使用 cmd 2>&1 | cat >> file 而不是使用大括号或括号会更容易。对我来说,一旦您了解 cmd >> file 2>&1 的实现实际上是“将 STDOUT 重定向到 file”,然后是“将 STDERR 重定向到 STDOUT 当前指向的任何文件”(这显然是 {5 } 在第一次重定向之后),很明显您放置重定向的顺序。 UNIX 不支持重定向到流,仅支持流指向的 file 描述符。
F
F. Hauri - Give Up GitHub

来自脚本本身的重定向

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

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

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

ls -ld /tnt /tmp

运行此脚本将

如果 last.log 已经存在,则将它们重命名为 last.old(如果存在,则覆盖 last.old)。

创建一个新的 last.log。

将所有内容附加到overall.log

将所有内容输出到终端。

简单和组合的日志

#!/bin/bash

[ -e last.err ] && mv -f last.err lasterr.old
[ -e last.log ] && mv -f last.log lastlog.old

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

ls -ld /tnt /tmp

所以你有了

last.log 上次运行日志文件

last.err 上次运行错误文件

lastlog.old 以前的运行日志文件

lasterr.old 上一次运行错误文件

整体.log 附加的整体日志文件

general.err 附加的总体错误文件

combine.log 附加了整体错误和日志组合文件。

仍然输出到终端

对于交互式会话,请使用 stdbuf:

如果您打算在 interactive shell 中使用它,您必须告诉 tee 不要缓冲他的输入/输出:

# Source this to multi-log your session
[ -e last.err ] && mv -f last.err lasterr.old
[ -e last.log ] && mv -f last.log lastlog.old
exec 2> >(exec stdbuf -i0 -o0 tee -a overall.err combined.log /dev/tty >last.err)
exec 1> >(exec stdbuf -i0 -o0 tee -a overall.log combined.log /dev/tty >last.log)

一旦采购了这个,你可以尝试:

ls -ld /tnt /tmp

进一步查看:Pipe output to two different commands,然后点击 comment关于此副本的更详细答案的链接。
A
Ana Nimbus

如果您关心两个流内容的顺序,请参阅 @ed-morton 对类似问题 here 的回答。