Joe*_*nas 16 command-line shell bash io-redirection file-descriptors
当您重定向包含 exec 重定向的命令列表时,之后 exec >/dev/null 似乎不再应用,例如:
{ exec >/dev/null; } >/dev/null; echo "Hi"
Run Code Online (Sandbox Code Playgroud)
打印“嗨”。
我的印象是,{}
除非它是管道的一部分,否则命令列表不被视为子shell,因此exec >/dev/null
在我看来,它仍应应用于当前的 shell 环境中。
现在,如果您将其更改为:
{ exec >/dev/null; } 2>/dev/null; echo "Hi"
Run Code Online (Sandbox Code Playgroud)
没有预期的输出;文件描述符 1 仍然指向 /dev/null 以供将来的命令使用。这通过重新运行显示:
{ exec >/dev/null; } >/dev/null; echo "Hi"
Run Code Online (Sandbox Code Playgroud)
这不会给出任何输出。
我尝试制作一个脚本并跟踪它,但我仍然不确定这里到底发生了什么。
在这个脚本的每一点 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
Run Code Online (Sandbox Code Playgroud)
Ale*_*exP 18
让我们跟随
{ exec >/dev/null; } >/dev/null; echo "Hi"
Run Code Online (Sandbox Code Playgroud)
一步步。
有两个命令:
一种。{ exec >/dev/null; } >/dev/null
, 其次是
湾 echo "Hi"
shell 首先执行命令 (a),然后执行命令 (b)。
执行{ exec >/dev/null; } >/dev/null
收益如下:
一种。首先,shell 执行重定向>/dev/null
并记住在命令结束时撤消它。
湾 然后,shell 执行{ exec >/dev/null; }
.
C。最后,shell 将标准输出切换回原处。(这与中的机制相同ls -lR /usr/share/fonts >~/FontList.txt
——重定向仅在它们所属的命令的持续时间内进行。)
一旦第一个命令完成,shell 就会执行echo "Hi"
. 标准输出是第一个命令之前的任何地方。
Dep*_*iel 14
为了不使用子外壳或子进程,当复合列表的输出通过{}
管道传输时>
,外壳会在运行复合列表之前保存 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
Run Code Online (Sandbox Code Playgroud)
您可以看到,在第 134 行,描述符1
( STDOUT
) 被复制到至少具有索引的另一个描述符上10
(就是这样F_DUPFD
做的;它在复制到该描述符后返回从给定数字开始的最低可用描述符)。另请参阅第 137 行如何将open("/dev/null")
(descriptor 3
)的结果复制到描述符1
( STDOUT
) 上。最后,在线上147
,旧的STDOUT
保存在描述符上10
被复制回描述符1
( STDOUT
)。净效果是将更改隔离到STDOUT
在线144
(对应于内部exec >/dev/null
)。
{ exec >/dev/null; } >/dev/null; echo "Hi"
和之间的区别在于,{ exec >/dev/null; }; echo "Hi"
双重重定向dup2(10, 1);
在关闭 fd 10stdout
之前执行,这是原始文件的副本,在运行下一个命令 ( echo
) 之前。
之所以会这样,是因为外部重定向实际上覆盖了内部重定向。这就是为什么它在stdout
完成后复制回原始fd的原因。