$ cat x
cat: x: No such file or directory
$ cat y
This is y.
$ cat x y 1> hold 2>&1
cat: x: No such file or directory
This is y.
为什么stder也被重定向到hold?在将 stdout 重定向到 hold后, stder 被声明为 stdout ,并且在声明发生后没有更多的重定向。
$ cat x
cat: x: No such file or directory
$ cat y
This is y.
$ cat x y 1> hold 2>&1
cat: x: No such file or directory
This is y.
为什么stder也被重定向到hold?在将 stdout 重定向到 hold后, stder 被声明为 stdout ,并且在声明发生后没有更多的重定向。
TL;博士
回答
核心原因是因为在读取
1
时不再指标准输出2>&1
,而是指hold
文件,因为重定向是从左到右处理的。首先,请记住 Unix 环境中的所有命令都有标准流,这些流通过文件描述符 0 表示标准输入,1 表示标准输出,2 表示标准错误。当然也有极少数例外,但 99% 的情况下这是标准文件描述符。
重定向,例如
m>n
,m>&1
并m<n
执行系统调用dup2()
,该系统调用会复制文件描述符(也称为文件句柄)。Inm>n
,m
通常是一个文件描述符,n
可以是一个文件或另一个文件描述符。这正是2>&1
- 对对应于标准输入和标准输出的文件描述符的整数引用。发生时
cat x y 1> hold 2>&1
,shell 首先将打开hold
文件,并通过下一个可用的文件描述符引用它,通常是3
,然后通过执行该文件描述符的复制dup2(3,1)
。dup2()
syscall 有点像cp
命令,你有cp old copy
. 因此,现在文件描述符1
指的是相同的打开文件描述(struct file
在 Linux 内核中也称为),独立于其他文件描述符 3。当
2>&1
看到时,shell 执行 seconddup2(1,2)
。所以现在文件描述符 2 是文件描述符 1 的独立副本,但之前2>&1
看到的是什么?1
已经指向打开的文件hold
。从那里 shell 将执行fork
和execve
系统调用以实际cat
作为子进程运行,它将继承打开的文件描述符。但就命令而言,在这种情况下
cat
,它会写入文件描述符 1 和 2,而不会意识到它们是其他内容的副本。您可以使用命令查看所有这些操作
strace
:边注:
如果本意是让
stderr
显示在屏幕上,那么2>&1
可以从命令中删除。cat x y > hold
足以将标准输出发送到hold
文件并将标准错误发送到屏幕。如果打算
stderr
通过stdin
管道发送,我们将需要交换文件描述符这基本上执行如下交换: