重定向运算符是否始终并行打开文件描述符?

重定向运算符是否始终并行打开文件描述符?

1. 考虑片段#1:

$ cat test.txt > test.txt
cat: test.txt: input file is output file

似乎cat使其输入文件描述符指向 test.txt,然后当它尝试将其输出文件描述符设置为 test.txt 时,它会抛出上述错误。这里似乎cat知道重定向运算符,因此继续尝试将输出文件描述符设置为 test.txt

2. 考虑片段#2:

$ cat 1.txt
1:CAT
2:dog
$ sed 's/cat/CAT/g' test.txt
1:CAT
2:dog
$ sed 's/cat/CAT/g' test.txt > test.txt
$ cat test.txt # Note that test.txt is now empty
$

在这里我们看到以sed读取模式打开 test.txt (最后一个参数),同时设置test.txt为其文件输出描述符。此外,操作员会在开始读取'>'文件之前覆盖文件的内容。sed

我知道管道中的命令是并行执行的,但没有遇到任何有关重定向运算符如何行为的信息。任何支持链接都会有所帮助。

答案1

除了 jordanm 指出的文档之外,我想确保纠正您的问题中说明的误解 - 执行的程序确实不是处理重定向。它甚至几乎不知道它们。shell 处理重定向。

程序启动时打开三个文件:stdin (#0)、stdout (#1) 和 stderr (#2)。如果您只是从 shell 提示符运行一个程序,这些程序将连接到您的终端设备,因此该程序会读取您键入的内容 (stdin),并将输出 (stdout) 和错误 (stderr) 打印到您的终端。

举个例子,我只是cat在终端中运行(上面tty写着 is /dev/pts/31)。我可以检查它打开了哪些文件lsof

$ lsof -a -p `pidof cat` -d0,1,2
COMMAND   PID    USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
cat     21257 anthony    0u   CHR 136,31      0t0   34 /dev/pts/31
cat     21257 anthony    1u   CHR 136,31      0t0   34 /dev/pts/31
cat     21257 anthony    2u   CHR 136,31      0t0   34 /dev/pts/31

事实上,我们可以看到它的终端对所有三个都开放。现在,让我们尝试一个相当愚蠢的 cat 调用:cat < /dev/zero > /dev/null 2>/dev/full,它重定向所有三个:

COMMAND   PID    USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
cat     21838 anthony    0r   CHR    1,5      0t0 1030 /dev/zero
cat     21838 anthony    1w   CHR    1,3      0t0 1028 /dev/null
cat     21838 anthony    2w   CHR    1,7      0t0 1031 /dev/full

shell 通过将三个设备作为 stdin、stdout 和 stderr(而不是终端)传递来实现这些重定向。 shell 类似地实现了管道。让我们尝试一下cat | dd > /dev/null(确实是一个相当愚蠢的管道):

COMMAND   PID    USER   FD   TYPE DEVICE SIZE/OFF     NODE NAME
cat     22507 anthony    0u   CHR 136,31      0t0       34 /dev/pts/31
cat     22507 anthony    1w  FIFO    0,8      0t0 56081395 pipe
cat     22507 anthony    2u   CHR 136,31      0t0       34 /dev/pts/31

COMMAND   PID    USER   FD   TYPE DEVICE SIZE/OFF     NODE NAME
dd      22508 anthony    0r  FIFO    0,8      0t0 56081395 pipe
dd      22508 anthony    1u   CHR 136,31      0t0       34 /dev/null
dd      22508 anthony    2u   CHR 136,31      0t0       34 /dev/pts/31

请注意 shell 如何打开一个管道,并使用它来将 的 stdout 连接cat到 的 stdin dd。以及它如何将ddstdout 连接到/dev/null.

正在运行的命令并不真正了解重定向。他们只是像平常一样使用 stdin、stdout、stderr。这些都可以是终端,或者它们可以被重定向到/来自文件、设备,或者可能是到另一个程序的管道。或者甚至是网络套接字(如果您的 shell 支持的话)。

即使是最复杂的管道实际上也只是向 shell 发出的指令,说明如何在执行程序之前连接这三个文件句柄。

(注意:某些程序在其中一个连接到终端的情况下表现不同,但这通常在交互式使用中更加用户友好。例如,ls当标准输出不是一个时,切换到单列输出并且没有颜色终端——如果您要将其传递给另一个程序,这通常是您想要的。如果 stdin 不是终端,某些程序会以不同的方式处理提示,等等。)

答案2

发生重定向第一的在壳里。在你的例子中:

cat test.txt > test.txt

首先发生的是 bash opens test.txt,它会截断文件。现在它是空的,之前作为参数cat执行。test.txt

从 bash 联机帮助页的重定向部分:

在执行命令之前,可以使用 shell 解释的特殊符号来重定向其输入和输出。重定向还可用于打开和关闭当前 shell 执行环境的文件。以下重定向运算符可以出现在简单命令之前或出现在命令内的任何位置,也可以跟随在命令之后。重定向按照它们出现的顺序从左到右进行处理。

我在 POSIX 规范中没有看到任何内容表明它应该首先发生,但我不知道有哪个 shell 不这样做。

相关内容