答案1
2.7.6 复制输出文件描述符
重定向运算符:
[n]>&word
应从另一个输出文件描述符复制一个输出文件描述符,或将关闭一个。如果 word 的计算结果为一位或多位数字,则 n 表示的文件描述符或标准输出(如果未指定 n)应成为 word 表示的文件描述符的副本;如果 word 中的数字不代表已打开用于输出的文件描述符,则会导致重定向错误;请参阅 Shell 错误的后果。如果 word 的计算结果为“-”,则关闭文件描述符 n 或标准输出(如果未指定 n)。尝试关闭未打开的文件描述符不应构成错误。如果 word 的计算结果为其他内容,则行为未指定。
(强调我的)。
So>&-
是1>&-
1 的缩写,1 是 stdout 的文件描述符。
所以在:
cmd >&-
shellclose(1)
在稍后执行的进程中执行此操作cmd
。因此,cmd
它的 fd 0 和 2 可能会在启动时打开(因为它们分别是 stdin 和 stderr,它们像 stdout 一样通常并且按照惯例总是在某个东西上,因为这是命令期望默认情况下接受输入并默认发送错误的地方),但FD 1关闭了。文件描述符 3 及以上也可能被关闭。
这意味着如果cmd
打开了某个文件(如open("some-file", O_WRONLY, 0600)
),它将在第一个空闲文件描述符上打开,并且 fd 将为 1!这意味着这some-file
将成为其标准输出随后的位置。例如,如果printf("Please enter your name: ")
稍后发生,则会进入some-file
!
这曾经是(几十年来)利用 setuid 可执行文件的一种方法。
例如,chsh
setuid 可执行文件可/etc/passwd
根据用户的请求进行编辑以更改用户的登录 shell。
和:
chsh >&-
chsh
最终可能会/etc/passwd
在 fd 1 上打开 a 并将其打算发送给用户的消息写在那里。
意识到这个问题后,软件开始通过在启动时检查 fd 0、1 或 2 是否关闭来防止这种情况,如果是,/dev/null
则打开它们。
我似乎记得这就是 GNU libc(GNU 系统上的任何可执行文件使用的)在检测到它至少作为 setuid 可执行文件或其他敏感上下文运行时所做的事情,但我现在无法验证它,所以要么我记错了,要么他们改变了行为。
无论如何,如果你tee
现在查看 GNU 的源代码,在打开文件进行写入时,它使用fopen_safer()
gnulib(不是 GNU libc)的变体fopen()
,如果使用的 fd fopen()
<= 2,则将其移动到高于 2 的 fd。
因此,在 中,至少tee aa bb cc >&-
通过该实现,将在 2 以上的第一个 fd(可能是 3)上打开,在下一个 fd 上打开,依此类推,并且 1 保持关闭,所以我们看到:tee
aa
bb
$ echo test | tee aa bb cc >&-
tee: 'standard output': Bad file descriptor
tee
当 fd 1 (stdout) 关闭时无法写入时,Where会报告错误。
对于tee
来自 busybox 的,它不使用fopen_safer()
来自 gnulib 的,
$ echo test | busybox tee aa bb cc >&-
$ cat aa
test
test
aa
实际上是在 fd 1 上,这解释了为什么test
在其中写入了两次:一次是因为它是在 stdout(fd 1)上写入的,另一次是因为它被写入了打开的 fd aa
(也是 fd 1)。
我希望它清楚地表明,如果您想丢弃tee
的标准输出(或任何命令),您应该不是关闭它,但将其重定向到 /dev/null :
echo test | tee aa bb cc > /dev/null
不过,在这里,您可以这样做,而不是丢弃它:
echo test | tee aa bb > cc
echo test | tee aa bb cc >&-
仅适用于那些tee
检测到 stdout 已关闭并自行在 /dev/null 上重新打开它的实现,作为一种保护措施,如上所述,这既不是 GNUtee
也不是 busybox的情况tee
(至少在链接到当前版本的 GNU 时)库)。
您可能想要关闭 stdout(或 stdin 或 stderr)的一些原因:
利用 setuid/setgid 或更一般的软件,这些软件在执行时会提升其特权,但在这些病态情况下表现不佳。
测试您的软件以检查它在这些情况下表现正常。
in
zsh
,cmd1 > file | cmd2
将 的输出发送cmd1
到两者file
并通过管道发送到cmd2
(类似于cmd1 | tee file | cmd2
除了tee
ing 是在内部完成并且更灵活。cmd1
如果您想将stderr 发送到cmd2
并将 stdout 发送到 ,这会妨碍您将 stderr 发送到file
但将 stdout 发送到和。cmd1 2>&1 > file | cmd2
cmd2
file
cmd2
tee
可以通过执行以下操作来禁用该ing:cmd1 2>&1 >&- > file | cmd2
虽然你也可以这样做:
{ cmd1 2>&1 > file; } | cmd2
或者
cmd1 > file 2> >(cmd2)
或1<&-
就此而言,>&-
和之间的唯一区别<&-
是它们在未指定时操作的 fd(分别为 1 和 0)。他们都只是做了一个close(fd)
。