将 stdout 声明为 stder

将 stdout 声明为 stder
$ 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>&1hold

首先,请记住,Unix 环境中的所有命令都有标准流,这些流通过文件描述符引用:0 表示 stdin,1 表示 stdout,2 表示 stderr。当然,也有一些罕见的例外,但 99% 的情况下这是标准文件描述符。

重定向(例如m>nm>&1m<n执行系统调用 dup2()复制文件描述符(又名文件句柄)。在 中m>nm通常是文件描述符,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 fileLinux 内核)独立于其他文件描述符 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

相关内容