如何使用Bash将stdout和stderr重定向并附加到文件中?

fly*_*ire 1440 linux bash redirect pipe stream

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

cmd > file.txt
Run Code Online (Sandbox Code Playgroud)

要在Bash中重定向stdout,追加到文件,我知道要使用:

cmd >> file.txt
Run Code Online (Sandbox Code Playgroud)

要将stdoutstderr重定向到截断的文件,我知道要使用:

cmd &> file.txt
Run Code Online (Sandbox Code Playgroud)

如何重定向附加到文件的stdoutstderrcmd &>> file.txt不适合我.

Ale*_*lli 1881

cmd >>file.txt 2>&1
Run Code Online (Sandbox Code Playgroud)

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

  1. >>file.txt:file.txt在追加模式下打开并重定向stdout.
  2. 2>&1:重定向stderr" stdout当前的位置".在这种情况下,这是一个以追加模式打开的文件.换句话说,&1重用stdout当前使用的文件描述符.

  • 它是简单的重定向,重定向语句一如既往地从左到右进行评估.>>文件:红色.STDOUT to file(追加模式)(1 >>文件的缩写)2>&1:红色.STDERR到"stdout去哪里"请注意,解释"将STDERR重定向到STDOUT"是错误的. (176认同)
  • 如果你做`cmd >> file1 2 >> file2`它应该达到你想要的. (40认同)
  • 效果很好!但有没有办法理解这一点,还是应该像对待原子bash一样对待它? (31认同)
  • 它说"将输出(stdout,文件描述符1)附加到file.txt并将stderr(文件描述符2)发送到与fd1相同的位置". (28认同)
  • 只是为了向像我这样的用户强调这一点:重定向的顺序很重要,2>&1 应该在 >>file.txt 之后。 (5认同)
  • 太多的标点符号使这种语法变得困难。您可能熟悉文件描述符“1”=“stdout”和“2”=“stderr”。`stdout` 是默认值(高于 `stderr`),因此排在第一位。重定向 stderr 时,必须将 2 放在“>”之前,否则一旦遇到“>”,就会被解释为重定向“stdout”。您的编程经验可能会帮助您记住“&”作为引用字符,因此“&1”类似于“指向 1 的指针”。希望这个细分提供了一些助记符,可以帮助记忆,而无需再次进行 SO 搜索;) (3认同)
  • @TheBonsai然而如果我需要将STDERR重定向到另一个文件但附加怎么办?这可能吗? (2认同)
  • 我在当前目录中有 file1.txt 和 file2.txt 。当我运行: 1. ls file{1..5}.txt 1>res.txt 2>&1:它为 res.txt 提供正确的输出但是当我写入 2. ls file{1..5}.txt 1 >res.txt 2>res.txt:现在它不会向 res.txt 提供正确的输出,在 res.txt 中,与我们不重定向任何流时应有的输出相比,省略了一些输出。为什么 ? (2认同)

The*_*sai 348

有两种方法可以执行此操作,具体取决于您的Bash版本.

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

cmd >> outfile 2>&1
Run Code Online (Sandbox Code Playgroud)

非便携的方式,开始猛砸4

cmd &>> outfile
Run Code Online (Sandbox Code Playgroud)

(比喻&> outfile)

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

  • 决定可移植性是否值得关注(然后使用经典方式)
  • 决定是否可移植性甚至是Bash pre-4是一个问题(然后使用经典方式)
  • 无论你使用哪种语法,都不要在同一个脚本中更改它(混乱!)

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

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

这里描述的语法是(除了其他重定向语法之外):http://bash-hackers.org/wiki/doku.php/syntax/redirection#appending_redirected_output_and_error_output

  • 我更喜欢&>>因为它与&>和>>一致.读取"将输出和错误附加到此文件"也比"将错误发送到输出,将输出附加到此文件"更容易.请注意,虽然Linux通常具有当前版本的bash,OS X,但在撰写本文时,仍需要通过自制软件等手动安装bash 4. (8认同)
  • @zsero cron根本不使用bash ......它使用`sh`.您可以通过将`SHELL =/bin/bash`添加到`crontab -e`文件来更改默认shell. (3认同)
  • 同样需要注意的是,在 cron 作业中,即使您的系统具有 Bash 4,您也必须使用 pre-4 语法。 (2认同)

Aar*_* R. 87

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

cmd >log.out 2>log_error.out
Run Code Online (Sandbox Code Playgroud)

附加将是:

cmd >>log.out 2>>log_error.out
Run Code Online (Sandbox Code Playgroud)

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

A B*_*A B 63

在Bash 4(以及ZSH 4.3.11)中:

cmd &>>outfile
Run Code Online (Sandbox Code Playgroud)

刚开箱即用

  • @mikemaccana:[TheBonsai的答案](http://stackoverflow.com/a/876267/4279)显示自2009年以来的bash 4解决方案 (10认同)
  • 为什么这个答案包含在 TheBonsai 的答案中时仍然存在?请考虑删除它。您将获得[纪律徽章](https://meta.stackexchange.com/questions/7609/what-is-the- Purpose-of-the-disciplined-badge)。 (3认同)
  • @all:这是一个很好的答案,因为它适用于bash而且很简短,所以我编辑了以确保它明确地提到了bash. (2认同)

Pra*_*ami 50

这应该工作正常:

your_command 2>&1 | tee -a file.txt
Run Code Online (Sandbox Code Playgroud)

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

  • 如果您也想在终端中查看输出,这就是正确的答案。然而,这并不是最初提出的问题。 (2认同)

Qui*_*hou 24

试试这个

You_command 1>output.log  2>&1
Run Code Online (Sandbox Code Playgroud)

您对&> x.file的使用在bash4中有效.对不起:(

这里有一些额外的提示.

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

0代表stdin,1代表stdout,2代表stderror.任何其他临时使用都可以使用3~9.

可以使用operator >>>(append)将任何文件描述符重定向到其他文件描述符或文件.

用法:< 类file_descriptor > > < 文件名 | &file_descriptor >

请参阅http://www.tldp.org/LDP/abs/html/io-redirection.html

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

F. *_*uri 20

一些评论和有用的技巧

介绍

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

ls -ld /tmp /tnt
Run Code Online (Sandbox Code Playgroud)

用于同时填充STDINSTDERR。(希望您的root中没有任何名为 的条目。)tnt

来自脚本本身的重定向

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

ls -ld /tmp /tnt
Run Code Online (Sandbox Code Playgroud)

运行此命令将创建/追加logfile.txt,包含:

#!/bin/bash

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

/bin/ls -ld /tmp /tnt
Run Code Online (Sandbox Code Playgroud)

或者

/bin/ls: cannot access '/tnt': No such file or directory
drwxrwxrwt 2 root root 4096 Apr  5 11:20 /tmp
Run Code Online (Sandbox Code Playgroud)

创建或附加标准输出logfile.txt以及创建或附加错误输出errfile.txt.

记录到许多不同的文件

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

#!/bin/bash

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

/bin/ls -ld /tmp /tnt
Run Code Online (Sandbox Code Playgroud)

运行这个脚本将会

  • 如果lastlog.txt已经存在,则将它们重命名为lastlog.oldlastlog.old如果存在则覆盖)。
  • 创建一个新的lastlog.txt
  • 将所有内容附加到overall.log
  • 将所有内容输出到终端。

简单日志组合日志

#!/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
Run Code Online (Sandbox Code Playgroud)

所以你有了

  • lastlog.txt上次运行日志文件
  • lasterr.txt上次运行错误文件
  • lastlog.old之前的运行日志文件
  • lasterr.old上次运行错误文件
  • overall.log附加整体日志文件
  • overall.err附加总体错误文件
  • combined.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
Run Code Online (Sandbox Code Playgroud)
  • 我使用它是$sTime为了确保两个文件将呈现完全相同时间戳。(如果$EPOCHSECONDS调用两次,则每次扩展之间的它们可能会有所不同!)
  • 我添加了当前 pid的 8 个字符的十六进制表示形式:$$以确保唯一性。

第三次运行后,您必须找到 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
Run Code Online (Sandbox Code Playgroud)

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

对于Fonic的评论,经过一番测试,我不得不同意:teestdbuf是没用的。但 ...

如果您打算在*交互式* shell 中使用它,您必须告诉 `tee` 不要缓冲他的输入/输出:
# 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)
Run Code Online (Sandbox Code Playgroud)

一旦找到了这个,你可以尝试:

ls -ld /tnt /tmp
Run Code Online (Sandbox Code Playgroud)

更复杂的样本

来自我关于如何将 Unix 时间戳转换为日期字符串的3 条评论

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

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 -
Run Code Online (Sandbox Code Playgroud)

date,stdbuf被要求...

exec关于和命令的一些解释stdbuf

  • forks通过使用或$(...)运行subshel​​l<(...)来完成,它将在另一个子shell ( subsubshel ​​l ) 中执行二进制文件。该命令告诉 shell 脚本中没有更多命令要运行,因此二进制( ) 将作为替换进程同一级别执行(无需保留更多内存来运行另一个子进程)。execstdbuf ... tee

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

        exec [-cl] [-a name] [command [arguments]]
               If  command  is  specified,  it  replaces the
               shell.  No new process is created....
    
    Run Code Online (Sandbox Code Playgroud)

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

  • 来自stdbuf的手册页:

    NAME
           stdbuf  -  Run COMMAND, with modified buffering
           operations for its standard streams.
    
    Run Code Online (Sandbox Code Playgroud)

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

  • 您能解释一下“exec stdbuf”在这种情况下有何帮助吗?`stdbuf` 的手册页指出 if 对 `tee` 没有任何影响? (4认同)
  • @Fonic ***关于 exec 和 stdbuf 命令的一些解释***,已发布! (3认同)

jam*_*lin 7

令我惊讶的是,近十年来,还没有人发布这种方法:

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

(cmd 2>&1) >> file.txt
Run Code Online (Sandbox Code Playgroud)

这产生了一个subshel​​l,因此它的效率不如的传统方法高cmd >> file.txt 2>&1,但这种方法对我来说更自然,更容易理解:

  1. 将stderr重定向到stdout。
  2. 通过附加到文件来重定向新的标准输出。

此外,括号消除了任何歧义的顺序,尤其是如果您想将stdout和stderr用管道传递给另一个命令时,尤其如此。

  • @MikkoRantalainen 我已经用避免生成子 shell 的变体更新了我的答案。 (3认同)
  • 这一实现会导致系统运行一个额外的进程。使用语法“cmd &gt;&gt; file 2&gt;&amp;1”适用于所有 shell,并且不需要额外的进程来运行。 (2认同)
  • @MikkoRantalainen 我已经解释过它会生成一个子 shell 并且效率较低。这种方法的要点是,如果效率不是什么大问题(而且很少是大问题),那么这种方法更容易记住并且更难出错。 (2认同)