实际用于移动文件描述符


16

根据bash手册页:

重定向运算符

   [n]<&digit-

将文件描述符移动digit到文件描述符n,如果n未指定,则将标准输入(文件描述符0)移动 到文件描述符。digit复制到后关闭n

将文件描述符“移动”到另一个文件描述符是什么意思?这种做法的典型情况是什么?

Answers:


14

3>&4-是bsh也支持的ksh93扩展,它是的缩写3>&4 4>&-,即3现在指向4过去的位置,而4现在已关闭,因此4所指向的内容现在移至3。

典型的用法是在您复制stdinstdout保存它的副本并想要还原它的情况下,例如:

假设您要捕获命令的stderr(仅stderr),而将stdout留在变量中。

命令替换var=$(cmd),创建管道。管道的写入端变为cmdstdout(文件描述符1),而另一端由Shell读取以填充变量。

现在,如果要stderr转到变量,可以执行:var=$(cmd 2>&1)。现在,fd 1(stdout)和2(stderr)都进入管道(并最终到达变量),这只是我们想要的一半。

如果这样做var=$(cmd 2>&1-)(是的简写var=$(cmd 2>&1 >&-),则现在只有cmdstderr进入管道,而fd 1关闭。如果cmd尝试写入任何输出,则返回EBADF错误,如果打开文件,它将获得第一个空闲fd,并且打开的文件将被分配给该文件,stdout除非命令对此加以防范!也不是我们想要的。

如果我们希望cmd单独保留stdout ,即指向它在命令替换外部指向的相同资源,那么我们需要以某种方式将该资源带入命令替换内部。为此,我们可以在命令替换stdout 外部进行复制以将其放入内部。

{
  var=$(cmd)
} 3>&1

哪种写方法更干净:

exec 3>&1
var=$(cmd)
exec 3>&-

(这还具有还原fd 3而不是最终将其关闭的好处)。

然后,在{(或exec 3>&1)直到之前},fd 1和3都指向最初指向的同一资源fd 1。fd 3还将指向命令替换中的该资源(命令替换仅重定向fd 1,stdout)。因此,对于cmd,我们有fds 1、2、3:

  1. 通往var的管道
  2. 不变
  3. 与1指向外部命令替换的内容相同

如果我们将其更改为:

{
  var=$(cmd 2>&1 >&3)
} 3>&1-

然后变成:

  1. 与1指向外部命令替换的内容相同
  2. 通往var的管道
  3. 与1指向外部命令替换的内容相同

现在,我们有了所需的东西:stderr进入管道,stdout保持不变。但是,我们将fd 3泄漏给cmd

尽管命令(按照惯例)假定fds 0到2是打开的并且是标准输入,输出和错误,但它们不假定其他任何fds。他们很可能会保留3美元不变。如果他们需要另一个文件描述符,则只需执行一个操作即可open()/dup()/socket()...返回第一个可用的文件描述符。如果(像需要这样做的shell脚本一样exec 3>&1)他们需要fd专门使用它,则他们将首先将其分配给某些东西(并且在该过程中,fd 3持有的资源将由该过程释放)。

最好关闭fd 3,因为cmd它没有使用它,但是如果在调用之前将其分配给您,则没什么大不了的cmd。问题可能是:(cmd以及它可能产生的其他进程)可用的fd少了。一个潜在的更严重的问题是,该fd指向的资源是否最终可能由该资源cmd在后台生成的进程所持有。如果该资源是管道或其他进程间通信通道(例如,当您的脚本以方式运行时script_output=$(your-script))可能会引起关注,因为这将意味着从另一端读取的进程在此之前永远不会看到文件结尾。后台进程终止。

所以在这里,最好写:

{
  var=$(cmd 2>&1 >&3 3>&-)
} 3>&1

其中,具有bash可缩短到:

{
  var=$(cmd 2>&1 >&3-)
} 3>&1

总结一下为什么很少使用它的原因:

  1. 这是非标准的,只是语法糖。您必须在节省一些击键与使脚本的可移植性和对那些不习惯该罕见功能的用户不那么明显的平衡之间取得平衡。
  2. 通常,在复制原始fd之后关闭它的需要经常被忽略,因为在大多数情况下,我们不会受到后果的困扰,因此我们只需要>&3代替>&3-or即可>&3 3>&-

正如您发现的那样,它很少使用的证明是bash虚假的。在bash compound-command 3>&4-any-builtin 3>&4-树叶的fd 4即使关闭compound-commandany-builtin返回。一个补丁来解决这个问题,现在(2013年2月19日)提供。


谢谢,现在我知道移动fd是什么了。关于第二个片段,我有4个基本问题(通常是fds):1)在cmd1中,您将2(stderr)复制为3,如果命令在内部使用此3 fd怎么办?2)为什么3>&1和4>&1起作用?复制3和4仅在这两个cmd中生效,当前的shell也会受到影响吗?3)为什么要关闭cmd1中的4和cmd2中的3?这些命令不使用提到的fds,对吗?4)在最后一个片段中,如果将fd复制到不存在的fd(在cmd1和cmd2中),我分别表示3和4,会发生什么?
昆汀

@Quentin,我使用了一个更简单的示例,并对其进行了更多解释,希望它现在提出的问题少于其回答的数量。如果你还有没有直接关系到FD移动语法问题,我建议你问一个单独的问题
斯特凡Chazelas

{ var=$(cmd 2>&1 >&3) ; } 3>&1-收盘1是错字吗?
昆汀

@Quentin,这是在我不认为我打算把它列入一个错字,但它使因为没有括号内使用没什么区别在于的fd 1(这是它复制与FD 3的整点:因为原来的1否则将无法在内访问$(...))。
斯特凡Chazelas

1
@Quentin进入后{...},fd 3指向fd 1以前指向的位置,并且fd 1关闭,然后在进入时$(...),fd 1设置为进给的管道$var,然后也将其设置为cmd2,然后再将1指向3点,即外部1。事后b仍然关闭的事实是bash中的错误,我将进行报告。该功能来自的ksh93没有该错误。
斯特凡Chazelas

4

这意味着要使其指向其他文件描述符所在的相同位置。你需要这样做很少,除了标准错误描述的明显不同的处理(stderrfd 2/dev/stderr -> /proc/self/fd/2)。在某些复杂的情况下,它可以派上用场。

Advanced Bash Scripting指南包含以下较长的日志级别示例和以下代码段:

# Redirecting only stderr to a pipe.
exec 3>&1                              # Save current "value" of stdout.
ls -l 2>&1 >&3 3>&- | grep bad 3>&-    # Close fd 3 for 'grep' (but not 'ls').
#              ^^^^   ^^^^
exec 3>&-                              # Now close it for the remainder of the script.

例如,在Source Mage的法术中,我们使用它来识别同一代码块的不同输出:

  (
    # everything is set, so run the actual build infrastructure
    run_build
  ) 3> >(tee -a $C_LOG >> /dev/stdout) \
    2> >(tee -a $C_LOG 1>&2 > $VOYEUR_STDERR) \
     > >(tee -a $C_LOG > $VOYEUR_STDOUT)

由于日志记录的原因,它附加了额外的过程替代项(VOYEUR决定是将数据显示在屏幕上还是仅记录日志),但是始终需要显示一些消息。为此,我们将它们打印到文件描述符3中,然后对其进行特殊处理。


0

在Unix中,文件由文件描述符处理(较小的整数,例如,标准输入为0,标准输出为1,标准错误为2;当您打开其他文件时,通常会为它们分配最小的未使用描述符)。因此,如果您知道该程序的内幕,并且想将文件描述符5的输出发送到标准输出,则可以将描述符5移到1。这就是错误的2> errors来源,结构喜欢2>&1将错误复制到输出流。

因此,几乎从未使用过(我隐约记得在我25年以上几乎完全使用Unix的情况下一次或两次愤怒地使用它),但是在需要时绝对必要。


但是为什么不以以下方式复制文件描述符1:5>&1?我不明白移动FD的用处是什么,因为内核将在之后立即关闭FD
Quentin

这不会将5复制为1,而是将5发送到1所要去的地方。在你问之前 是的,有多种不同的表示法,它们是从各种前体外壳中提取的。
vonbrand

还是不明白。如果5>&1将5发送到1会去的地方,那么1>&5-除了关闭5之外,究竟会做什么?
昆汀,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.