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

发布于 2024-07-19 08:36:49 字数 541 浏览 13 评论 0原文

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

cmd > file.txt

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

cmd >> file.txt

重定向标准输出和 标准错误到截断的文件,我知道使用:

cmd &> file.txt

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

To redirect standard output to a truncated file in Bash, I know to use:

cmd > file.txt

To redirect standard output in Bash, appending to a file, I know to use:

cmd >> file.txt

To redirect both standard output and standard error to a truncated file, I know to use:

cmd &> file.txt

How do I redirect both standard output and standard error appending to a file? cmd &>> file.txt did not work for me.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

诗化ㄋ丶相逢 2024-07-26 08:36:50
cmd >>file.txt 2>&1

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

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

Bash executes the redirects from left to right as follows:

  1. >>file.txt: Open file.txt in append mode and redirect stdout there.
  2. 2>&1: Redirect stderr to "where stdout is currently going". In this case, that is a file opened in append mode. In other words, the &1 reuses the file descriptor which stdout currently uses.
掩耳倾听 2024-07-26 08:36:50

有两种方法可以执行此操作,具体取决于您的 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 中进行了描述。

There are two ways to do this, depending on your Bash version.

The classic and portable (Bash pre-4) way is:

cmd >> outfile 2>&1

A nonportable way, starting with Bash 4 is

cmd &>> outfile

(analog to &> outfile)

For good coding style, you should

  • decide if portability is a concern (then use the classic way)
  • decide if portability even to Bash pre-4 is a concern (then use the classic way)
  • no matter which syntax you use, don't change it within the same script (confusion!)

If your script already starts with #!/bin/sh (no matter if intended or not), then the Bash 4 solution, and in general any Bash-specific code, is not the way to go.

Also remember that Bash 4 &>> is just shorter syntax — it does not introduce any new functionality or anything like that.

The syntax is (beside other redirection syntax) described in the Bash hackers wiki.

强辩 2024-07-26 08:36:50

在 Bash 中,您还可以显式指定对不同文件的重定向:

cmd >log.out 2>log_error.out

追加将是:

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

In Bash you can also explicitly specify your redirects to different files:

cmd >log.out 2>log_error.out

Appending would be:

cmd >>log.out 2>>log_error.out
灯下孤影 2024-07-26 08:36:50

这应该可以正常工作:

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

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

This should work fine:

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

It will store all logs in file.txt as well as dump them in the terminal.

凌乱心跳 2024-07-26 08:36:50

在 Bash 4 中(以及 Z shell (zsh) 4.3。 11):

cmd &>> outfile

刚开箱。

In Bash 4 (as well as Z shell (zsh) 4.3.11):

cmd &>> outfile

just out of box.

滥情稳全场 2024-07-26 08:36:50

试试这个:

You_command 1> output.log  2>&1

您对 > 的用法 x.file 在 Bash 4 中可以工作。抱歉:(

这里有一些额外的提示。0

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

代表 标准输入,1代表标准输出,2代表标准错误。3~9可供任何其他临时使用。

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


<file_descriptor> > <文件名 | &file_descriptor>

请参阅第 20 章 I/O 重定向

Try this:

You_command 1> output.log  2>&1

Your usage of &> x.file does work in Bash 4. Sorry for that: (

Here comes some additional tips.

0, 1, 2, ..., 9 are file descriptors in bash.

0 stands for standard input, 1 stands for standard output, 2 stands for standard error. 3~9 is spare for any other temporary usage.

Any file descriptor can be redirected to other file descriptor or file by using operator > or >>(append).

Usage:
<file_descriptor> > <filename | &file_descriptor>

Please see the reference in Chapter 20. I/O Redirection.

空袭的梦i 2024-07-26 08:36:50

一些评论和有用的技巧

简介

来自What does " 2>&1 "mean?,我将在这个答案中使用命令:

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被调用两次,它们在每次扩展之间可能会有所不同!)
  • 我添加了当前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

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

关于Fonic' comment 经过一些测试,我必须同意:使用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)

一旦来源这个,你可以尝试:

ls -ld /tnt /tmp

更复杂的示例

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

我使用了更复杂的命令来实时解析和重组 squid 的日志:每行都以 UNIX EPOCH 开头em> 以毫秒为单位,我在第一个点上分割线,在 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命令的一些解释:

  • 运行<使用 $(...)<(...) 进行 code>forks 是通过运行 subshel​​l 来完成的将在另一个子shellsubsubshel​​l)中执行二进制文件exec 命令告诉 shell 脚本中没有更多命令要运行,因此将执行 binary (stdbuf ... tee)作为替换进程,处于同一级别(无需预留更多内存来运行另一个子进程)。

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

    <块引用>

     exec [-cl] [-a 名称] [命令 [参数]] 
                 如果指定了命令,它将替换 
                 壳。   没有创建新进程.... 
      

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

  • 来自 stdbuf 的手册页:

    <块引用>
    <前><代码>名称
    stdbuf - 运行命令,并修改缓冲
    其标准流的操作。

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

Some remarks and useful tricks

Introduction

From What does " 2>&1 " mean?, I will use in this answer the command:

ls -ld /tmp /tnt

for populating simultaneously both STDIN and STDERR. (In the hope you don't have any entry in your root, called tnt.)

Redirections from script himself

You could plan redirections from the script itself:

#!/bin/bash

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

/bin/ls -ld /tmp /tnt

Running this will create/append logfile.txt, containing:

/bin/ls: cannot access '/tnt': No such file or directory
drwxrwxrwt 2 root root 4096 Apr  5 11:20 /tmp

Or

#!/bin/bash

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

/bin/ls -ld /tmp /tnt

While create or append standard output to logfile.txt and create or append errors output to errfile.txt.

Log to many different files

You could create two different logfiles, appending to one overall log and recreating another last 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

Running this script will

  • if lastlog.txt already exist, rename them to lastlog.old (overwriting lastlog.old if they exist).
  • create a new lastlog.txt.
  • append everything to overall.log
  • output everything to the terminal.

Simple and combined logs

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

So you have

  • lastlog.txt last run log file
  • lasterr.txt last run error file
  • lastlog.old previous run log file
  • lasterr.old previous run error file
  • overall.log appended overall log file
  • overall.err appended overall error file
  • combined.log appended overall error and log combined file.
  • still output to the terminal

Same, using timestamped log filenames:

#!/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}
  • I use $sTime in order to ensure both file will present exactly same timestamp. (If $EPOCHSECONDS is invoked two times, they could differ between each expansion!)
  • I add formed 8 characters hexadecimal representation of currend pid: $$ in order to ensure unicity.

after 3rd run, you must found 9 files:

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

And for interactive session, use stdbuf:

Regarding Fonic' comment and after some test, I have to agree: with tee, stdbuf is useless. But ...


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

More complex sample

From my 3 remarks about how to Convert Unix timestamp to a date string

I've used more complex command to parse and reassemble squid's log in real time: As each line begin by an UNIX EPOCH with milliseconds, I split the line on 1st dot, add @ symbol before EPOCH SECONDS to
pass them to date -f - +%F\ %T then reassemble date's output and the rest of line with a dot by using paste -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 -

With date, stdbuf was required...

Some explanations about exec and stdbuf commands:

  • Running forks by using $(...) or <(...) is done by running subshell wich will execute binaries in another subshell (subsubshell). The exec command tell shell that there are not further command in script to be run, so binary (stdbuf ... tee) will be executed as replacement process, at same level (no need to reserve more memory for running another sub-process).

    From bash's man page (man -P'less +/^\ *exec\ ' bash):

        exec [-cl] [-a name] [command [arguments]]
               If  command  is  specified,  it  replaces the
               shell.  No new process is created....
    

    This is not really needed, but reduce system footprint.

  • From stdbuf's man page:

    NAME
           stdbuf  -  Run COMMAND, with modified buffering
           operations for its standard streams.
    

    This will tell system to use unbuffered I/O for tee command. So all outputs will be updated immediately, when some input are coming.

自找没趣 2024-07-26 08:36:50

另一种方法:

如果使用旧版本的 Bash,其中 &>> 不可用,您还可以这样做:

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

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

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

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

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

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

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

Another approach:

If using older versions of Bash where &>> isn't available, you also can do:

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

This spawns a subshell, so it's less efficient than the traditional approach of cmd >> file.txt 2>&1, and it consequently won't work for commands that need to modify the current shell (e.g. cd, pushd), but this approach feels more natural and understandable to me:

  1. Redirect standard error to standard output.
  2. Redirect the new standard output by appending to a file.

Also, the parentheses remove any ambiguity of order, especially if you want to pipe standard output and standard error to another command instead.

To avoid starting a subshell, you instead could use curly braces instead of parentheses to create a group command:

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

(Note that a semicolon (or newline) is required to terminate the group command.)

‖放下 2024-07-26 08:36:50

这真是太好了!

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

参考https://stackoverflow.com/a/314678/5449346,非常简单干净,它重定向了所有脚本的输出到日志文件和stdout,包括脚本中调用的脚本:

执行> >(tee -a "logs/logdata.log") 2>&1 在屏幕上打印日志并将其写入文件 – shriyog 2017 年 2 月 2 日,地址:
9:20

通常我们会将其中之一放置在脚本的顶部或附近。 解析命令行的脚本将在解析后进行重定向。

将标准输出发送到文件

执行> 带有 stderr 的文件

执行> 文件
exec 2>&1 将 stdout 和 stderr 附加到文件

执行>> file exec 2>&1 正如 Jonathan Leffler 在他的评论中提到的:

exec 有两个独立的工作。 第一个是替换当前的
使用新程序执行 shell(脚本)。 另一个是改变
当前 shell 中的 I/O 重定向。 这是通过具有
没有参数执行。

This is terribly good!

Redirect the output to log file and stdout within the current script.

Refer to https://stackoverflow.com/a/314678/5449346, very simple and clean, it redirects all the script's output to the log file and stdout, including the scripts called in the script:

exec > >(tee -a "logs/logdata.log") 2>&1 prints the logs on the screen as well as writes them into a file – shriyog Feb 2, 2017 at
9:20

Typically we would place one of these at or near the top of the script. Scripts that parse their command lines would do the redirection after parsing.

Send stdout to a file

exec > file with stderr

exec > file
exec 2>&1 append both stdout and stderr to file

exec >> file exec 2>&1 As Jonathan Leffler mentioned in his comment:

exec has two separate jobs. The first one is to replace the currently
executing shell (script) with a new program. The other is changing the
I/O redirections in the current shell. This is distinguished by having
no argument to exec.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文