问题描述
我今天试图将stdout
和stderr
重定向到一个文件,我遇到了这个:
<command> > file.txt 2>&1
这首先将stderr
重定向至stdout
,然后将所得的stdout
重定向至file.txt
。
但是,为什么不是订单<command> 2>&1 > file.txt
?人们自然会将其视为(假设从左到右执行)首先执行的命令,stderr
被重定向到stdout
,然后,生成的stdout
被写入file.txt
。但以上仅将stderr
重定向到屏幕。
shell如何解释这两个命令?
最佳解决方案
运行<command> 2>&1 > file.txt
时,stderr被2>&1
重定向到stdout当前所在的位置,即终端。之后,stdout被>
重定向到文件,但是stderr没有被重定向,因此保留为终端输出。
使用<command> > file.txt 2>&1
stdout首先由>
重定向到文件,然后2>&1
将stderr重定向到stdout所在的位置,即文件。
这可能看起来反直觉,但是当你以这种方式想到重定向时,并记住它们是从左到右处理的,这更有意义。
次佳解决方案
如果你追查它可能是有道理的。
在开始时,stderr和stdout会转到相同的东西(通常是终端,我在这里称之为pts
):
fd/0 -> pts
fd/1 -> pts
fd/2 -> pts
我在这里用文件描述符编号引用stdin,stdout和stderr:它们分别是文件描述符0,1和2。
现在,在第一组重定向中,我们有> file.txt
和2>&1
。
所以:
-
> file.txt
:fd/1
现在转到file.txt
。使用>
时,如果未指定任何内容,则1
是隐含的文件描述符,因此这是1>file.txt
:fd/0 -> pts fd/1 -> file.txt fd/2 -> pts
-
2>&1
:fd/2
现在可以到达fd/1
目前的任何地方:fd/0 -> pts fd/1 -> file.txt fd/2 -> file.txt
另一方面,使用2>&1 > file.txt
,订单被逆转:
-
2>&1
:fd/2
现在可以到达fd/1
当前的任何地方,这意味着没有任何变化:fd/0 -> pts fd/1 -> pts fd/2 -> pts
-
> file.txt
:fd/1
现在转到file.txt
:fd/0 -> pts fd/1 -> file.txt fd/2 -> pts
重要的一点是,重定向并不意味着重定向文件描述符将遵循目标文件描述符的所有未来更改;它只会呈现当前状态。
第三种解决方案
我认为shell会首先在左侧设置重定向,并在设置下一个重定向之前完成它。
William Shotts的The Linux Command Line说
First we redirect standard output to the file, and then we redirect file descriptor 2 (standard error) to file descriptor one (standard output)
这是有道理的,但随后
Notice that the order of the redirections is significant. The redirection of standard error must always occur after redirecting standard output or it doesn’t work
但实际上,我们可以在将stderr重定向到具有相同效果的文件后将stdout重定向到stderr
$ uname -r 2>/dev/null 1>&2
$
因此,在command > file 2>&1
中,shell将stdout发送到文件,然后将stderr发送到stdout(正在发送到文件)。然而,在command 2>&1 > file
中,shell首先将stderr重定向到stdout(即在stdout正常运行的终端中显示它),然后将stdout重定向到文件。 TLCL误导我们必须首先重定向stdout:因为我们可以先将stderr重定向到文件然后再将stdout发送给它。在重定向到文件之前,我们不能做的是将stdout重定向到stderr,反之亦然。另一个例子
$ strace uname -r 1>&2 2> /dev/null
4.8.0-30-generic
我们可能会认为这会将stdout处理到与stderr相同的位置,但它没有,它首先将stdout重定向到stderr(屏幕),然后只重定向stderr,就像我们尝试反过来一样…
我希望这会带来一点点光明……
第四种方案
你已经得到了一些非常好的答案。让我强调一下,这里涉及两个不同的概念,对它的理解有很大帮助:
背景:文件描述符与文件表
您的文件描述符只是一个数字0 … n,它是进程中文件描述符表中的索引。按照惯例,STDIN = 0,STDOUT = 1,STDERR = 2(注意这里的术语STDIN
等只是某些编程语言和手册页中常规使用的符号/宏,没有一个名为STDIN的实际”object”;对于本讨论的目的,STDIN为0,等等。
该文件描述符表本身不包含任何有关实际文件的信息。相反,它包含指向不同文件表的指针;后者包含有关实际物理文件(或块设备,或管道,或Linux可通过文件机制解决的任何其他内容)和更多信息(即,是否用于读取或写入)的信息。
因此,当您在shell中使用>
或<
时,只需替换相应文件描述符的指针即可指向其他内容。语法2>&1
只是将描述符2指向1点的任何地方。 > file.txt
只需打开file.txt
进行写入,然后让STDOUT(文件decsriptor 1)指向它。
还有其他好东西,例如2>(xxx)
(即:创建一个运行xxx
的新进程,创建一个管道,将新进程的文件描述符0连接到管道的读取端,并将原始进程的文件描述符2连接到管道的写入端)。
这也是除shell之外的其他软件中“文件句柄魔术”的基础。例如,您可以在Perl脚本中将STDOUT文件描述符复制到另一个(临时)文件描述符中,然后将re-open STDOUT复制到新创建的临时文件中。从现在开始,来自您自己的Perl脚本的所有STDOUT输出和该脚本的所有 system()调用都将在该临时文件中结束。完成后,您可以将您的STDOUT re-dup添加到您保存到的临时描述符中,并且presto,一切都和以前一样。您甚至可以同时写入该临时描述符,因此当您的实际STDOUT输出转到临时文件时,您仍然可以实际将输出内容输出到真正的STDOUT(通常是用户)。
Answer
要将上面给出的背景信息应用于您的问题:
In what order does the shell execute commands and stream redirection?
左到右。
<command> > file.txt 2>&1
-
fork
关闭了一个新流程。 -
打开
file.txt
并将其指针存储在文件描述符1(STDOUT)中。 -
将STDERR(文件描述符2)指向现在指向的fd 1(当然也是已经打开的
file.txt
)。 -
exec
<command>
This apparently redirects stderr to stdout first, and then the resulting stdout is redirected to file.txt.
如果只有一个表,这将是有意义的,但如上所述有两个。文件描述符不是递归地指向彼此,认为“将STDERR重定向到STDOUT”是没有意义的。正确的想法是“将STDERR指向STDOUT指向的任何地方”。如果您稍后更改STDOUT,STDERR将保持原样,它不会神奇地继续进行STDOUT的进一步更改。
第五种方案
订单是从左到右。 Bash的手册已经涵盖了你的要求。从手册的REDIRECTION
部分引用:
Redirections are processed in the
order they appear, from left to right.
几行之后:
Note that the order of redirections is signifi‐
cant. For example, the command
ls > dirlist 2>&1
directs both standard output and standard error
to the file dirlist, while the command
ls 2>&1 > dirlist
directs only the standard output to file
dirlist, because the standard error was dupli‐
cated from the standard output before the stan‐
dard output was redirected to dirlist.
重要的是要注意在任何命令运行之前首先解决重定向!请参阅https://askubuntu.com/a/728386/295286
第六种方案
它总是从左到右……除了
就像在Math中一样,我们从左到右除了乘法和除法在加法和减法之前完成,除了括号内的运算(+ – )将在乘法和除法之前完成。
根据这里的Bash初学者指南(Bash Beginners Guide),首先是(从左到右)的8层次的层次结构:
-
支撑扩展”{}”
-
Tilde扩展”~”
-
Shell参数和变量表达式”$”
-
命令替换”$(command)”
-
算术表达式”$((EXPRESSION))”
-
我们在这里讨论的过程替换”(LIST)”
-
Word拆分“'< space>< tab>< newline>’”
-
文件名扩展”*”,”?”等
所以它总是从右到右……除非……