移动文件描述符的实际用途

移动文件描述符的实际用途

根据 bash 手册页:

重定向运算符

   [n]<&digit-

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

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

答案1

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

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

假设您想要捕获命令的 stderr(并且仅捕获 stderr),同时将 stdout 单独保留在变量中。

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

现在,如果你想stderr访问该变量,你可以这样做var=$(cmd 2>&1):现在 fd 1 (stdout) 和 2 (stderr) 都进入管道(并最终进入变量),这只是我们想要的一半。

如果我们这样做var=$(cmd 2>&1-)(缩写为var=$(cmd 2>&1 >&-),现在只有cmd的 stderr 进入管道,但 fd 1 被关闭。如果cmd尝试写入任何输出,则会返回错误EBADF,如果它打开一个文件,它将获得第一个空闲的 fd,并且打开的文件将被分配给它,stdout除非命令防止这种情况发生!也不是我们想要的。

如果我们希望 stdoutcmd保持独立,即指向它在命令替换之外指向的相同资源,那么我们需要以某种方式将该资源带入命令替换内部。为此,我们可以复制一份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 的任何内容。他们很可能会保持 fd 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

其中, withbash可以缩短为:

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

总结一下很少使用的原因:

  1. 它是非标准的,只是语法糖。您必须在节省一些击键次数与降低脚本的可移植性以及对于不习惯这种不常见功能的人来说不那么明显之间取得平衡。
  2. 复制后关闭原始 fd 的需要经常被忽视,因为大多数时候,我们不会遭受后果,所以我们只是这样做>&3而不是>&3-or >&3 3>&-

证明它很少被使用,正如您发现的那样bash 中的伪造。在 bashcompound-command 3>&4-any-builtin 3>&4-离开 fd 4 即使在compound-commandorany-builtin已返回之后也已关闭。 A修补现在(2013-02-19)可以解决该问题。

答案2

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

高级 Bash 脚本指南有这个更长的日志级别示例和这个片段:

# 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's Sorcery 中,我们使用它来辨别同一代码块的不同输出:

  (
    # 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 中,然后对其进行特殊处理。

答案3

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

因此,几乎从未使用过(在我 25 年以上几乎只使用 Unix 的时间里,我隐约记得在愤怒中使用过一两次),但在需要时绝对必要。

相关内容