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


32

我今天试图将两者stdoutstderr文件都重定向,但遇到了这个问题:

<command> > file.txt 2>&1

显然,这将首先重定向stderrstdout,然后将结果stdout重定向到file.txt

但是,为什么不下订单<command> 2>&1 > file.txt?人们自然会将其读为(假设从左到右执行)先执行的命令,然后将stderr其重定向到stdout,然后将结果stdout写入file.txt。但是以上内容仅重定向stderr到屏幕。

Shell如何解释这两个命令?


7
TLCL表示“首先将标准输出重定向到文件,然后将文件描述符2(标准错误)重定向到文件描述符1(标准输出)”和“请注意重定向的顺序很重要。标准错误的重定向必须始终在重定向标准输出后发生,否则将不起作用”
Zanna 2016年

@Zanna:是的,我的问题恰恰来自于TLCL中的阅读!:)我有兴趣知道为什么它不起作用,即外壳通常如何解释命令。
训练Heartnet '16

好吧,在您的问题中,您说相反的“这显然首先将stderr重定向到stdout ...”-我从TLCL了解到,shell将stdout发送到文件,然后将 stderr发送到stdout(即到文件)。我的解释是,如果将stderr发送到stdout,它将显示在终端中,并且随后stdout的重定向将不包括stderr(即,在stdout重定向发生之前stderr到屏幕的重定向完成了吗?)
Zanna

7
我知道这是一种老式的说法,但是您的Shell附带了说明这些内容bash手册 -例如手册中的重定向。顺便说一句,重定向不是命令。
Reinier Post

在设置重定向之前无法执行该命令:当execv调用-family syscall实际将子进程交给正在启动的命令时,shell就会退出循环(该进程中不再有执行代码) ),并且无法控制从那时起发生的事情;重定向因此都必须被执行开始之前进行,而外壳在运行的过程中其自身的副本它fork()编在运行命令。
查尔斯·达菲

Answers:


41

当您运行<command> 2>&1 > file.txtstderr时,会将其重定向2>&1到stdout当前所在的位置。之后,stdout将通过重定向到文件>,但stderr不会随之重定向,因此保留为终端输出。

使用<command> > file.txt 2>&1stdout首先通过重定向到文件>,然后2>&1将stderr重定向到stdout要去的位置,即文件。

开始时似乎很直观,但是当您以这种方式考虑重定向时,请记住从左到右处理它们会更有意义。


这是有道理的,如果你认为在文件描述符的条款和为了执行“DUP / fdreopen”的呼声向右
前标记考恩

20

如果将其找出来,这可能是有道理的。

一开始,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。当没有指定任何内容时>,With 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

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


谢谢,这似乎是一个更自然的解释!:)尽管在2.第二部分中,您有轻微的错别字;fd/1 -> file.txt而不是fd/2 -> file.txt
训练Heartnet '16

11

我认为这有助于外壳程序首先在左侧设置重定向,并在设置下一个重定向之前完成它。

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 > fileshell中,首先将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重定向,就像我们以另一种方式尝试时一样...

我希望这能带来一点光...


如此雄辩!
Arronical

啊,现在我明白了!非常感谢@Zanna和@Arronical!刚开始我的命令行之旅。:)
训练Heartnet,2013年

@TrainHeartnet很高兴!希望您和我一样喜欢它:D
Zanna

@Zanna:确实,我是!:D
训练Heartnet,2013年

2
@TrainHeartnet不用担心,充满挫折和欢乐的世界正等着您!
Arronical

10

您已经得到了一些非常好的答案。让我强调一下,尽管这里涉及两个不同的概念,但对它们的理解会极大地帮助您:

背景:文件描述符与文件表

您的文件描述符只是一个数字0 ... n,它是您进程中文件描述符表中的索引。按照约定,STDIN = 0,STDOUT = 1,STDERR = 2(请注意,STDIN此处的术语等只是某些编程语言和手册页中约定使用的符号/宏,没有称为STDIN的实际“对象”;对于本讨论的目的,STDIN 0等)。

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

因此,当在外壳中使用><时,只需替换相应文件描述符的指针以指向其他对象即可。该语法2>&1只是将描述符2指向1的任何位置。> file.txt只需打开即可file.txt进行写操作,并让STDOUT(文件描述符1)指向该文件。

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

这也是Shell以外的其他软件中“文件处理魔术”的基础。例如,您可以在Perl脚本dup中将STDOUT文件描述符链接到另一个(临时)文件描述符中,然后将STDOUT重新打开到新创建的临时文件中。从这一点开始,您自己的Perl脚本的所有STDOUT输出以及system()该脚本的所有调用将最终存储在该临时文件中。完成后,您可以dup将STDOUT 重新保存到保存到其中的临时描述符,并且所有操作都像以前一样。您甚至可以同时写入该临时描述符,因此,即使您实际的STDOUT输出进入临时文件,您仍然可以将内容实际输出至实际的 STDOUT(通常是用户)。

回答

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

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

左到右。

<command> > file.txt 2>&1

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

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

如果只有一个表,这将是有意义的,但是如上所述,有两个表。文件描述符不是递归指向彼此的,因此认为“将STDERR重定向到STDOUT”是没有意义的。正确的想法是“将STDERR指向STDOUT指向的任何位置”。如果稍后更改STDOUT,则STDERR会保留在原位置,不会神奇地伴随对STDOUT的进一步更改。


支持“文件处理魔术”位-尽管它不能直接回答我今天学到的新知识的问题……
Floris

3

顺序是从左到右。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


3

它总是从左到右...除非

就像在数学中一样,我们从左到右进行操作,只不过在加法和减法之前完成乘法和除法,除了括号(+-)内的运算要在乘法和除法之前完成。

根据此处的Bash初学者指南(Bash初学者指南),首先出现的层次结构(从左到右)有8个层次:

  1. 大括号扩展“ {}”
  2. 波浪号扩展“〜”
  3. Shell参数和变量表达式“ $”
  4. 命令替换“ $(command)”
  5. 算术表达式“ $((EXPRESSION))”
  6. 处理替换我们在这里所说的 “ <(LIST)”或“>(LIST)”
  7. 单词拆分“'<空格> <选项卡> <换行符>'”
  8. 文件名扩展“ *”,“?”等

所以它总是从左到右...除非是...


1
需要明确的是:进程替换和重定向是两个独立的操作。您可以一无所有。具有流程替代并不意味着bash决定更改其处理重定向的顺序
muru
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.