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

Que*_*tin 16 bash io-redirection file-descriptors

根据 bash 手册页:

重定向运算符

   [n]<&digit-
Run Code Online (Sandbox Code Playgroud)

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

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

Sté*_*las 14

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

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

假设您想捕获命令的 stderr(并且仅限于 stderr),而将 stdout 单独留在变量中。

命令替换var=$(cmd),创建一个管道。管道的写入端成为cmd的标准输出(文件描述符 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除非命令对此进行防范!也不是我们想要的。

如果我们想让 stdout ofcmd保持不变,即指向它在命令替换之外指向的相同资源,那么我们需要以某种方式将该资源引入命令替换内。为此,我们可以在命令替换stdout 之外进行复制以将其带入。

{
  var=$(cmd)
} 3>&1
Run Code Online (Sandbox Code Playgroud)

这是一种更简洁的写法:

exec 3>&1
var=$(cmd)
exec 3>&-
Run Code Online (Sandbox Code Playgroud)

(这也有恢复 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-
Run Code Online (Sandbox Code Playgroud)

然后就变成了:

  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
Run Code Online (Sandbox Code Playgroud)

其中,withbash可以缩短为:

{
  var=$(cmd 2>&1 >&3-)
} 3>&1
Run Code Online (Sandbox Code Playgroud)

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

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

证明它很少使用,正如您发现的那样,它在 bash 中伪造的。在 bashcompound-command 3>&4-any-builtin 3>&4-离开 fd 4 之后compound-commandany-builtin已经返回。一个补丁来解决这个问题,现在(2013年2月19日)提供。