在Bash中重定向stderr和stdout

863

4
我想说这是一个令人惊讶的有用问题。很多人不知道如何做到这一点,因为他们不需要经常这样做,而且这也不是Bash最好记录的行为。 - Robert Wm Ruedisueli
4
有时候看到输出结果(通常情况下)并将其重定向到一个文件中也很有用。请参考下面Marko的答案。(我在这里说这句话是因为如果第一个被接受的答案足以解决问题,那么只看第一个答案很容易忽略其他提供有用信息的答案。) - jvriesem
15个回答

952

请看这里。正确的写法应该是:

yourcommand &> filename

它会将标准输出和标准错误都重定向到文件filename中。


36
根据Bash Hackers Wiki,此语法已被弃用。是这样吗? - salmatron
23
根据http://wiki.bash-hackers.org/scripting/obsolete的内容,它似乎已经过时了,因为它不是POSIX的一部分,但bash man页面没有提到它会在不久的将来从bash中移除。该手册确实指定'&>'优先于'>&',这两者在其他方面是等效的。 - chepner
13
我猜我们不应该使用 &>,因为它不符合POSIX标准,常见的shell如“dash”也不支持它。 - Sam Watkins
33
重点提示:如果您在脚本中使用此命令,请确保以 #!/bin/bash 开始,而不是 #!/bin/sh,因为它需要 bash。 - Tor Klingberg
16
使用“或 &>>”来追加内容而非覆盖原有内容。 - Alexander Gonchiy
显示剩余4条评论

561
do_something 2>&1 | tee -a some_file

这会将标准错误重定向到标准输出,并将标准输出重定向到some_file同时将其打印到标准输出。


19
在AIX(ksh)上,你的解决方案可行。被接受的答案“do_something &>filename”不可行。+1。 - Withheld
14
@Daniel,但这个问题特别涉及到Bash。 - John La Rooy
3
我收到了“模棱两可的输出重定向”(Ambiguous output redirect)的错误信息。你有任何想法是为什么吗? - Alexandre Holden Daly
13
请注意(默认情况下)这会产生一个副作用,即 $? 不再指 do_something 的退出状态,而是指 tee 的退出状态。 - Flimm
1
我需要在身上纹上这个,我总是记不住字符的顺序! - rob
显示剩余8条评论

328

你可以将stderr重定向到stdout,并将stdout重定向到文件:

some_command >file.log 2>&1

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

这种格式比最流行的&>格式更受欢迎,后者只适用于Bash。在Bourne shell中,它可能被解释为在后台运行命令。此外,该格式更易读 - 2(标准错误)被重定向到1(标准输出)。


1
这种方法相比于 some_command &> file.log 有什么优势? - ubermonkey
8
如果您想追加内容到文件中,那么必须按照以下方式操作:echo "foo" 2>&1 1>> bar.txt。据我所知,没有通过使用&>来进行追加的方法。 - SlappyTheFish
13
抱歉,这句话的意思是将字符串 "foo" 写入到名为 bar.txt 的文件中,并将标准输出和标准错误流都追加到该文件中。 - SlappyTheFish
37
我认为将2>&1解释为将标准错误输出重定向到标准输出并不准确;我认为更准确的说法是将标准错误输出发送到与此时此刻标准输出相同的位置。因此,在第一次重定向之后放置2>&1非常重要。 - jdg
2
@SlappyTheFish,实际上有一种方法:"&>>"来自bash man的说明: 追加标准输出和标准错误的格式为: &>>word 这在语义上等同于 >>word 2>&1 - Alexander Gonchiy
显示剩余5条评论

243
# Close standard output file descriptor
exec 1<&-
# Close standard error file descriptor
exec 2<&-

# Open standard output as $LOG_FILE file for read and write.
exec 1<>$LOG_FILE

# Redirect standard error to standard output
exec 2>&1

echo "This line will appear in $LOG_FILE, not 'on screen'"

现在,一个简单的回显将写入$LOG_FILE,对于守护进程非常有用。
对于原帖作者,
这取决于你需要实现什么目标。如果你只需要从脚本中调用的命令重定向输入/输出,答案已经给出。我的回答是关于在当前脚本中重定向影响到所有命令/内置命令(包括forks)的代码片段。
另一个很酷的解决方案是将标准错误和标准输出同时重定向,并一次性记录到日志文件中,这涉及将 "流" 分成两部分。这个功能由 'tee' 命令提供,它可以同时写入/追加到多个文件描述符(文件、套接字、管道等): tee FILE1 FILE2 ... >(cmd1) >(cmd2) ...
exec 3>&1 4>&2 1> >(tee >(logger -i -t 'my_script_tag') >&3) 2> >(tee >(logger -i -t 'my_script_tag') >&4)
trap 'cleanup' INT QUIT TERM EXIT


get_pids_of_ppid() {
    local ppid="$1"

    RETVAL=''
    local pids=`ps x -o pid,ppid | awk "\\$2 == \\"$ppid\\" { print \\$1 }"`
    RETVAL="$pids"
}


# Needed to kill processes running in background
cleanup() {
    local current_pid element
    local pids=( "$$" )

    running_pids=("${pids[@]}")

    while :; do
        current_pid="${running_pids[0]}"
        [ -z "$current_pid" ] && break

        running_pids=("${running_pids[@]:1}")
        get_pids_of_ppid $current_pid
        local new_pids="$RETVAL"
        [ -z "$new_pids" ] && continue

        for element in $new_pids; do
            running_pids+=("$element")
            pids=("$element" "${pids[@]}")
        done
    done

    kill ${pids[@]} 2>/dev/null
}

所以,从一开始。假设我们有一个连接到/dev/stdout(文件描述符#1)和/dev/stderr(文件描述符#2)的终端。在实践中,它可以是管道,套接字或其他任何东西。

  • 创建文件描述符(FDs)#3和#4,并将它们指向与#1和#2相同的“位置”。更改文件描述符#1不会再影响文件描述符#3。现在,文件描述符#3和#4分别指向标准输出和标准错误。这些将用作真正的终端标准输出和标准错误。
  • 1> >(...)将标准输出重定向到括号中的命令
  • 括号(子shell)执行'tee',从exec的标准输出(管道)读取并通过另一个管道将其重定向到括号中的子shell中的'logger'命令。同时,它将相同的输入复制到文件描述符#3(终端)
  • 第二部分非常相似,是关于对标准错误和文件描述符#2和#4进行相同技巧的操作。

运行具有上述行以及以下行的脚本的结果:

echo "Will end up in standard output (terminal) and /var/log/messages"

...如下:

$ ./my_script
Will end up in standard output (terminal) and /var/log/messages

$ tail -n1 /var/log/messages
Sep 23 15:54:03 wks056 my_script_tag[11644]: Will end up in standard output (terminal) and /var/log/messages

如果您想看到更清晰的图片,请将以下两行代码添加到脚本中:
ls -l /proc/self/fd/
ps xf

5
虽然这是有条件的,但这是真的。我的方法是始终创建一个独特的、带时间戳的日志文件。另一种方法是追加。这两种方式都可以进行“日志轮换”。我更喜欢分开的文件,因为它们需要更少的解析,但正如我所说,任何能使你满意的方法都可以使用 :) - quizac
1
你的第二个解决方案很有信息量,但是清理代码是干嘛用的?它似乎不相关,如果是这样,只会混淆一个本来很好的例子。我也想看到稍微重新设计一下,使得FD 1和2不是被重定向到记录器,而是3和4,这样调用此脚本的任何东西都可以在常见假设stdout==1和stderr==2下进一步操作1和2,但我的简短实验表明这更加复杂。 - JFlo
1
我更喜欢带有清理代码的版本。这可能会稍微分散核心示例的注意力,但是剥离它将使示例不完整。网络上已经充满了没有错误处理的示例,或者至少友好地指出仍需要约一百行代码才能安全使用。 - Zoltan K.
1
那里的清理代码很必要,如果父PID被杀死或由于任何原因退出,则会删除后台进程。 'logger'进程可能会分离并继续运行,而父进程已经消失。此示例是更大脚本的一部分,由于偶发问题,我始终包括此代码片段(和其他代码片段)。 - quizac
3
我想详细说明一下清理代码。它是脚本的一部分,使得守护进程免受HANG-UP信号的影响。'tee'和'logger'是由相同的PPID生成的进程,并且它们从主bash脚本继承了HUP陷阱。因此,一旦主进程死亡,它们就会被init [1]继承。它们不会成为僵尸(defunc)。清理代码确保在主脚本死亡时杀死所有后台任务。它还适用于可能已经创建并在后台运行的任何其他进程。 - quizac
显示剩余4条评论

52
bash your_script.sh 1>file.log 2>&1

1>file.log指示 shell 将标准输出重定向到文件file.log2>&1告诉它将标准错误(文件描述符2)重定向到标准输出(文件描述符1)。

注意:顺序很重要,如liw.fi所指出的,2>&1 1>file.log不起作用。


对我来说,第二种方式更有意义。首先将所有stderr发送到stdout,然后将stdout发送到文件。为什么我们要在将stdout发送到文件之后将stderr发送到stdout? - Alaska

23

奇怪的是,这个有效:

yourcommand &> filename

但是这会导致语法错误:

yourcommand &>> filename
syntax error near unexpected token `>'

你必须使用:

yourcommand 1>> filename 2>&1

10
&>> 在 BASH 4 上可用:$ echo $BASH_VERSION 4.1.5(1)-release $ (echo to stdout; echo to stderr > /dev/stderr) &>> /dev/null注:将标准输出和标准错误重定向到 /dev/null - user272735

20

简短回答:Command >filename 2>&1Command &>filename


解释:

考虑以下代码,它将单词“stdout”打印到标准输出并将单词“stderror”打印到标准错误输出。

$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror
请注意,'&' 运算符告诉 bash 2 是一个文件描述符(指向 stderr),而不是文件名。如果我们省略 '&',此命令将把 'stdout' 打印到标准输出,并创建一个名为 "2" 的文件,并将 'stderror' 写入该文件中。
通过对上面的代码进行实验,您可以自己看到重定向运算符的工作方式。例如,通过更改哪个文件将两个描述符 1,2 重定向到 /dev/null,以下两行代码分别从 stdout 和 stderror 删除所有内容(打印剩余内容)。
$ (echo "stdout"; echo "stderror" >&2) 1>/dev/null
stderror
$ (echo "stdout"; echo "stderror" >&2) 2>/dev/null
stdout

现在,我们可以解释为什么以下代码的解决方案不会输出任何内容:

(echo "stdout"; echo "stderror" >&2) >/dev/null 2>&1

为了真正理解这一点,我强烈建议您阅读这个 有关文件描述符表的网页。 假设您已经阅读过了,我们可以继续。请注意,Bash从左到右处理; 因此Bash首先看到>/dev/null(与1>/dev/null相同),并将文件描述符1设置为指向/dev/null而不是标准输出。做完这个之后,Bash然后向右移动并看到2>&1。这将文件描述符2指向与文件描述符1相同的文件(而不是文件描述符1本身!!! (有关指针的更多信息,请参见此指针资源))。由于文件描述符1指向/dev/null,而文件描述符2指向与文件描述符1相同的文件,因此文件描述符2现在也指向/dev/null。因此,两个文件描述符都指向/dev/null,这就是为什么不会渲染任何输出的原因。


为了测试您是否真正理解该概念,请尝试猜测当我们切换重定向顺序时的输出:

(echo "stdout"; echo "stderror" >&2)  2>&1 >/dev/null

stderror

这里的原因是Bash从左到右进行评估,看到2>&1,因此设置文件描述符2指向与文件描述符1相同的位置,即标准输出stdout。然后将文件描述符1(记住>/dev/null = 1>/dev/null)设置为指向>/dev/null,从而删除通常发送到标准输出的所有内容。因此,我们剩下的就是子shell中未发送到标准输出的内容(圆括号中的代码),即"stderror"。 有趣的事情在于,即使1只是指向stdout的指针,通过2>&1将指针2重定向到1并不形成指针链2 -> 1 -> stdout。如果是这样的话,由于将1重定向到/dev/null,代码2>&1 >/dev/null会生成指针链2 -> 1 -> /dev/null,因此代码将不会产生任何输出,与上面所看到的不同。


最后,我要注意到有一种更简单的方法:

从3.6.4章节这里,我们可以看到可以使用运算符&>来重定向stdout和stderr。因此,要将任何命令的stderr和stdout输出都重定向到/dev/null(即删除输出),我们只需键入 $ command &> /dev/null 或者对于我的例子:

$ (echo "stdout"; echo "stderror" >&2) &>/dev/null

要点:

  • 文件描述符的行为类似于指针(尽管文件描述符不同于文件指针)
  • 将文件描述符“a”重定向到指向文件“f”的文件描述符“b”会导致文件描述符“a”指向与文件描述符b相同的位置 - 文件“f”。它不构成指针链a-> b-> f
  • 由于以上原因,顺序很重要,2>&1 >/dev/null != >/dev/null 2>&1。一个生成输出,而另一个则没有!

最后看一下这些很棒的资源:

Bash重定向文档文件描述符表解释指针入门


文件描述符(0、1、2)只是表中的偏移量。当使用2>&1时,效果是FD [2] = dup(1),因此无论FD [1] 指向哪里,FD [2] 现在都指向那里。当您将FD [1] 更改为指向/dev/null时,然后更改了FD [1],但不会更改FD [2] 插槽(它指向标准输出)。我使用dup()这个术语,因为这是用于复制文件描述符的系统调用。 - PatS

11
LOG_FACILITY="local7.notice"
LOG_TOPIC="my-prog-name"
LOG_TOPIC_OUT="$LOG_TOPIC-out[$$]"
LOG_TOPIC_ERR="$LOG_TOPIC-err[$$]"

exec 3>&1 > >(tee -a /dev/fd/3 | logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_OUT" )
exec 2> >(logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_ERR" )

相关内容:将标准输出和标准错误写入syslog

这个方法基本可行,但是在xinetd中无法正常工作 ;(


我猜测它不起作用是因为"/dev/fd/3权限被拒绝"。更改为>&3可能会有所帮助。 - quizac

10

我希望能将stdout和stderr的输出都记录在日志文件中,但是仍然需要让stderr在控制台上显示。因此,我需要通过tee命令来复制stderr的输出。

以下是我找到的解决方案:

command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
  • 首先交换标准错误输出和标准输出
  • 然后将标准输出添加到日志文件中
  • 将标准错误输出传输给 tee 命令,并将其附加到日志文件中

顺便提一下,这对我没用(日志文件为空)。|tee 没有效果。相反,我使用了 https://dev59.com/vHRB5IYBdhLWcg3wLk1M 来使其工作。 - Yaroslav Bulatov

9

在需要使用“piping”的情况下,您可以使用 |&

例如:

echo -ne "15\n100\n" | sort -c |& tee >sort_result.txt

或者

TIMEFORMAT=%R;for i in `seq 1 20` ; do time kubectl get pods | grep node >>js.log ; done |& sort -h

这些基于Bash的解决方案可以分别管道化标准输出和标准错误(从"sort -c"的标准错误,或从"sort -h"的标准错误)。


1
这实际上非常重要,但不太为人所知。做得好。您可能还想解释一下在管道与&结合使用时&的作用。 - not2qubit

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