当您重定向包含 exec 重定向的命令列表时,之后 exec >/dev/null 似乎不再适用,例如:
{ exec >/dev/null; } >/dev/null; echo "Hi"
打印出“嗨”。
我的印象是,{}
命令列表不被视为子 shell,除非它是管道的一部分,因此exec >/dev/null
在我看来,它仍然应该应用于当前的 shell 环境中。
现在如果你将其更改为:
{ exec >/dev/null; } 2>/dev/null; echo "Hi"
没有预期的输出;对于将来的命令,文件描述符 1 仍指向 /dev/null。重新运行可以显示这一点:
{ exec >/dev/null; } >/dev/null; echo "Hi"
这不会给出任何输出。
我尝试制作一个脚本并对其进行跟踪,但我仍然不确定这里到底发生了什么。
在此脚本中的每一点,STDOUT 文件描述符发生了什么?
编辑:添加我的 strace 输出:
read(255, "#!/usr/bin/env bash\n{ exec 1>/de"..., 65) = 65
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fcntl(1, F_GETFD) = 0
fcntl(1, F_DUPFD, 10) = 10
fcntl(1, F_GETFD) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
dup2(3, 1) = 1
close(3) = 0
close(10) = 0
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fcntl(1, F_GETFD) = 0
fcntl(1, F_DUPFD, 10) = 10
fcntl(1, F_GETFD) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
dup2(3, 1) = 1
close(3) = 0
dup2(10, 1) = 1
fcntl(10, F_GETFD) = 0x1 (flags FD_CLOEXEC)
close(10) = 0
fstat(1, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), ...}) = 0
ioctl(1, TCGETS, 0x7ffee027ef90) = -1 ENOTTY (Inappropriate ioctl for device)
write(1, "hi\n", 3) = 3
答案1
让我们跟随
{ exec >/dev/null; } >/dev/null; echo "Hi"
一步步。
有两个命令:
A。
{ exec >/dev/null; } >/dev/null
, 其次是b.
echo "Hi"
shell 首先执行命令 (a),然后执行命令 (b)。
执行
{ exec >/dev/null; } >/dev/null
收益情况如下:A。首先,shell执行重定向
>/dev/null
并记得在命令结束时撤消它。b.然后,shell 执行
{ exec >/dev/null; }
.C。最后,shell 将标准输出切换回原来的位置。 (这与
ls -lR /usr/share/fonts >~/FontList.txt
重定向仅在它们所属的命令的持续时间内进行相同的机制。)一旦第一个命令完成,shell 就会执行
echo "Hi"
。标准输出位于第一个命令之前的位置。
答案2
为了不使用子 shell 或子进程,当复合列表的输出{}
通过管道传输时>
,shell 在运行复合列表之前保存 STDOUT 描述符,并在运行后恢复它。因此,exec >
复合列表中的 不会将其影响超过旧描述符恢复为 STDOUT 的点。
我们来看看相关部分strace bash -c '{ exec >/dev/null; } >/dev/null; echo hi' 2>&1 | cat -n
:
132 open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
133 fcntl(1, F_GETFD) = 0
134 fcntl(1, F_DUPFD, 10) = 10
135 fcntl(1, F_GETFD) = 0
136 fcntl(10, F_SETFD, FD_CLOEXEC) = 0
137 dup2(3, 1) = 1
138 close(3) = 0
139 open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
140 fcntl(1, F_GETFD) = 0
141 fcntl(1, F_DUPFD, 10) = 11
142 fcntl(1, F_GETFD) = 0
143 fcntl(11, F_SETFD, FD_CLOEXEC) = 0
144 dup2(3, 1) = 1
145 close(3) = 0
146 close(11) = 0
147 dup2(10, 1) = 1
148 fcntl(10, F_GETFD) = 0x1 (flags FD_CLOEXEC)
149 close(10) = 0
您可以在第 134 行看到如何将描述符1
( STDOUT
) 复制到至少具有索引的另一个描述符上10
(这就是所做F_DUPFD
的;在复制到该描述符后,它返回从给定编号开始的最低可用描述符)。另请参阅第 137 行如何将open("/dev/null")
(descriptor )的结果3
复制到描述符1
( STDOUT
) 上。最后,在线 上147
,旧的STDOUT
保存在描述符上的内容10
被复制回描述符1
( STDOUT
) 上。最终效果是将更改隔离到STDOUT
线上144
(对应于内部exec >/dev/null
)。
答案3
{ exec >/dev/null; } >/dev/null; echo "Hi"
和之间的区别{ exec >/dev/null; }; echo "Hi"
在于,双重重定向是dup2(10, 1);
在关闭 fd 10(原始文件的副本)之前stdout
、运行下一个命令 ( echo
) 之前进行的。
之所以会发生这种情况,是因为外部重定向实际上覆盖了内部重定向。这就是为什么它stdout
在完成后会复制回原始fd。