$ 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?stder 被声明为 stdout后将 stdout 重定向到 hold 状态,并且声明发生后不再进行重定向。
答案1
总结
- 重定向按从左到右的顺序读取
- 读取重定向后,文件描述符不再指向之前的位置。
回答
其核心原因是因为读取1
时不再引用标准输出,而是引用文件,因为重定向是从左到右处理的。2>&1
hold
首先,请记住,Unix 环境中的所有命令都有标准流,这些流通过文件描述符引用:0 表示 stdin,1 表示 stdout,2 表示 stderr。当然,也有一些罕见的例外,但 99% 的情况下这是标准文件描述符。
重定向(例如m>n
、m>&1
和m<n
执行系统调用 dup2()
复制文件描述符(又名文件句柄)。在 中m>n
,m
通常是文件描述符,n
可以是文件或另一个文件描述符。这正是2>&1
—— 对应于 stdin 和 stdout 的文件描述符的整数引用。
发生时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 执行第二个dup2(1,2)
。因此,现在文件描述符 2 是文件描述符 1 的独立副本,但在2>&1
看到之前是什么?1
已经指向打开的文件hold
。从那里 shell 将执行系统fork
调用execve
以实际作为子进程运行cat
,它将继承打开的文件描述符。
但就命令而言,在这种情况下cat
,它会写入文件描述符 1 和 2,但并不知道它们是其他内容的副本。
您可以使用命令来查看所有这些操作strace
:
# ... is several irrelevant lines skipped of bash opening libraries
$ strace -f -edup2,openat,write bash -c 'cat testFile.txt > hold 2>&1'
...
strace: Process 17766 attached
[pid 17766] openat(AT_FDCWD, "hold", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
[pid 17766] dup2(3, 1) = 1
[pid 17766] dup2(1, 2) = 2
[pid 17766] openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
[pid 17766] openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
[pid 17766] openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
[pid 17766] openat(AT_FDCWD, "testFile.txt", O_RDONLY) = 3
[pid 17766] write(1, "potato\n\nNAME MAJ:MIN RM SIZE"..., 248) = 248
[pid 17766] +++ exited with 0 +++
边注:
如果最初的意图是让它stderr
显示在屏幕上,那么2>&1
可以从命令中删除。cat x y > hold
将 stdout 发送到hold
文件并将 stderr 发送到屏幕就足够了。
如果打算stderr
通过stdin
管道发送,我们需要交换文件描述符
$ cat x y 3>&2 2>&1 2>&3 2>hold | grep --color=always file
this is a test file y
$ cat hold
cat: x: No such file or directory
这基本上执行了如下交换:
# 3>&2 open new fd 3 , save copy of fd 2 there
dup2(2, 3) = 3
# 2>&1 , now turn 2 into copy of 1; 2 is still safe as fd 3
dup2(1, 2) = 2
# 2>&3 Now let's make 2 refer to what originally was 1, but now saved in 3
dup2(3, 2) = 2
# open file "hold" , which will be next available integer fd
openat(AT_FDCWD, "hold", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 4
# 2>hold
dup2(4, 2) = 2