shell以什么顺序执行命令和流重定向?

我今天试图将stdoutstderr重定向到一个文件,我遇到了这个:

  > file.txt 2>&1 

这显然首先将stderr重定向到stdout ,然后将生成的stdout重定向到file.txt

但是,为什么不是命令 2>&1 > file.txt ? 人们自然会将此读作(假设从左到右执行)首先执行的命令,将stderr重定向到stdout然后,将生成的stdout写入file.txt 。 但上面只将stderr重定向到屏幕。

shell如何解释这两个命令?

当你运行 2>&1 > file.txt stderr被2>&1重定向到stdout当前去的地方,你的终端。 之后,stdout被>重定向到文件,但是stderr没有被重定向,所以保持为终端输出。

使用 > 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.txt2>&1

所以:

  1. > file.txtfd/1现在转到file.txt 。 使用>1是隐含的文件描述符,当没有指定任何内容时,所以这是1>file.txt

     fd/0 -> pts fd/1 -> file.txt fd/2 -> pts 
  2. 2>&1fd/2现在转到fd/1 当前的位置:

     fd/0 -> pts fd/1 -> file.txt fd/2 -> file.txt 

另一方面,使用2>&1 > file.txt ,顺序颠倒:

  1. 2>&1fd/2现在转到fd/1当前的位置,这意味着没有任何变化:

     fd/0 -> pts fd/1 -> pts fd/2 -> pts 
  2. > file.txtfd/1现在转到file.txt

     fd/0 -> pts fd/1 -> file.txt fd/2 -> pts 

重要的一点是,重定向并不意味着重定向的文件描述符将遵循目标文件描述符的所有未来更改; 它只会呈现当前状态。

我认为shell首先会在左侧设置重定向,并在设置下一个重定向之前完成它。

William Shotts 的Linux命令行

首先我们将标准输出重定向到文件,然后我们将文件描述符2(标准错误)重定向到文件描述符1(标准输出)

这是有道理的,但随后

请注意,重定向的顺序很重要。 重定向标准输出后,必须始终发生标准错误的重定向,否则它将不起作用

但实际上,我们可以在将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的实际“对象”;出于本讨论的目的,STDIN 0,等等。

该文件描述符表本身不包含任何有关实际文件的信息。 相反,它包含指向不同文件表的指针; 后者包含有关实际物理文件(或块设备,或管道,或Linux可通过文件机制解决的任何其他内容)和更多信息(即,是否用于读取或写入)的信息。

因此,当您在shell中使用><时,只需替换相应文件描述符的指针即可指向其他内容。 语法2>&1只是将描述符2指向1点的任何地方。 > file.txt只是打开file.txt进行写入,让STDOUT(文件decsriptor 1)指向它。

还有其他好东西,例如2>(xxx) (即:创建一个运行xxx的新进程,创建一个管道,将新进程的文件描述符0连接到管道的读取端并连接原始进程的文件描述符2)到管道的写入端)。

这也是除shell之外的其他软件中“文件句柄魔术”的基础。 例如,您可以在Perl脚本中将STDOUT文件描述符复制到另一个(临时)文件描述符中,然后将STDOUT重新打开到新创建的临时文件中。 从现在开始,您自己的Perl脚本的所有STDOUT输出该脚本的所有system()调用都将在该临时文件中结束。 完成后,您可以将STDOUT重复到您保存到的临时描述符中,并且presto,一切都和以前一样。 您甚至可以同时写入该临时描述符,因此当您的实际STDOUT输出转到临时文件时,您仍然可以实际将输出内容输出到真正的 STDOUT(通常是用户)。

回答

要将上面给出的背景信息应用于您的问题:

shell以什么顺序执行命令和流重定向?

左到右。

> file.txt 2>&1

  1. fork一个新的过程。
  2. 打开file.txt并将其指针存储在文件描述符1(STDOUT)中。
  3. 将STDERR(文件描述符2)指向fd 1指向的任何内容(当然这也是已经打开的file.txt )。
  4. exec

这显然首先将stderr重定向到stdout,然后将生成的stdout重定向到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初学者指南 ),有8个顺序的第一个(从左到右):

  1. 支撑扩张“{}”
  2. Tilde扩展“〜”
  3. Shell参数和变量表达式“$”
  4. 命令替换“$(command)”
  5. 算术表达式“$((EXPRESSION))”
  6. 过程替换我们在这里谈论的内容 “<(LIST)”或“>(LIST)”
  7. 单词拆分“’ ‘”
  8. 文件名扩展“*”,“?”等

所以它总是从右到右……除非……