5

据我所知,进程替换 <(...) / >(...) 创建 fd

并将括号中的命令输出存储到生成的 fd 中。

因此,这两个命令是等价的

$ ls -al
$ cat <(ls -al)

在这里,我的问题是,生成的文件描述符保留多长时间?

我读过这篇文章,但似乎我的理解是错误的。

如果进程替换扩展为函数的参数,在调用函数期间扩展为环境变量,或扩展为函数内的任何赋值,则进程替换将“保持打开”以供函数内的任何命令使用或其被调用者,直到设置它的函数返回。如果在被调用者中再次设置相同的变量,除非新变量是本地的,否则先前的进程替换将关闭,并且在被调用者返回时对调用者不可用。

本质上,扩展为函数内变量的进程替换保持打开状态,直到发生进程替换的函数返回——即使分配给由函数调用者设置的局部变量也是如此。动态范围并不能保护它们免于关闭。

阅读后我的最佳猜测是,创建的 fd 在使用之前不会关闭。

由此,我写了一个非常愚蠢的代码,如下所示

#!/bin/bash

test_subs () {
  echo "Inside a function"
  FD2=<(ls -al)

  cat $FD1
  cat $FD2
}
FD1=<(ls -al)
test_subs

Result======================================
Inside a function
cat: /dev/fd/63: No such file or directory
cat: /dev/fd/63: No such file or directory

似乎新打开的 fd 在一行命令运行后就关闭了。

生成的fd维护多久,那么进程替换的范围是什么?

4

2 回答 2

6

TL;博士

似乎没有文档,因此无法保证流程替换的范围<(...)。我认为将进程替换保持在范围内的唯一安全方法是将它们直接定义为参数cmd <(...)、即时导出的变量VAR=<(...) cmd或重定向cmd < <(...)。以这种方式定义的进程替换在运行时仍然在范围内cmd

长篇大论

我像你一样解释了来自 Bash Hackers Wiki 的引用文章。同样,我得出了相同的结论,即在函数内声明进程替换的变量并不能保证它们保持打开状态。在某些系统上,还有许多其他方法可以让它们保持打开状态,尤其是使用命令组,如(...)subshel​​ls 和 contextes {...}。但是,这些技巧在某些系统上仍然失败。

除了链接的 Bash Hackers Wiki 中的错误评论外,我找不到任何相关文档。甚至bash 的手册也没有谈到进程替换的范围。所以我们被困在实验中(或阅读bash我没有的源代码)。

以下脚本创建了一些场景来检查进程替换何时<(...)保持在范围内。请注意,存在非常细微的差异。例如:使用在同一行中编写两个命令;还是在自己的行中编写每个命令都会有所不同。当然,这份清单并不完整。随意扩展它。

#! /usr/bin/env bash

echo 'define, use'
a=<(echo ok);
cat "$a"; unset a

echo 'define and use in same line'
a=<(echo ok); cat "$a"; unset a

echo 'define and use in subshell'
(a=<(echo ok);
cat "$a")

echo 'define and use in context'
{ a=<(echo ok)
cat "$a"; }; unset a

echo 'define and use in && chain'
a=<(echo ok) &&
cat "$a"; unset a

echo 'define in context and use in || chain'
{ a=<(echo ok); false; } || cat "$a"; unset a

echo 'define and use in for loop body'
for i in 1; do
  a=<(echo ok)
  cat "$a"
done

echo 'define and use in while loop head'
while
  a=<(echo ok)
  cat "$a"
  false
do true; done; unset a 

echo 'define and use in same case'
case x in
x)
  a=<(echo ok)
  cat "$a"
  ;;
esac; unset a

echo 'define in case, use in fall-through'
case x in
x)
    a=<(echo ok)
    ;&
y)
    cat "$a"
    ;;
esac; unset a

echo 'define and use inside function in same line'
f() { a=<(echo ok); cat "$a"; }; f; unset a f

echo 'define local and use inside function in same line'
f() { local a=<(echo ok); cat "$a"; }; f; unset a f

echo 'define, use as function argument'
f() { cat "$1"; }; a=<(echo ok)
f "$a"; unset a f

echo 'define, use as function argument in same line'
f() { cat "$1"; }; a=<(echo ok); f "$a"; unset a f

echo 'on-the-fly export, use in different shell'
a=<(echo ok) dash -c 'cat "$a"'

echo 'export, use in different shell'
export a=<(echo ok)
dash -c 'cat "$a"'; unset a

echo 'define in command substitution, use in parent in same line'
a=$(echo <(echo ok)); cat "$a"; unset a

echo 'read from here-string, use in parent in same line'
read a <<< <(echo ok); cat "$a"; unset a

echo 'read from process substitution, use in parent in same line'
read a < <(echo <(echo ok)); cat $a; unset a

echo 'read from pipe and use in same line'
shopt -s lastpipe; # TODO add `set +m` when running interactively
echo <(echo ok) | read -r a; cat "$a"
shopt -u lastpipe; unset a

echo 'define, unrelated read from file, use in same line'
a=<(echo ok); read < /etc/passwd; cat "$a"; unset a

echo 'define, unrelated read from process substitution, use in same line'
a=<(echo ok); read < <(echo unused); cat "$a"; unset a

echo 'define, unrelated cat from process substitution, use in same line'
a=<(echo ok); cat <(echo unused) > /dev/null; cat "$a"; unset a

echo 'define, unrelated read ... in subshell, use in same line'
a=<(echo ok); (read < <(echo unused)); cat "$a"; unset a b

echo 'define, unrelated read ... in command substitution, use in same line'
a=<(echo ok); b=$(read < <(echo unused)); cat "$a"; unset a b

# output can be prettified using
# ./script 2> /dev/null |
# awk 'p!="ok"{if($0=="ok")print "yes   " p;else print "no    " p}{p=$0}'

这些是我的系统的(美化的)输出

In scope on bash 5.0.17 on Arch Linux (kernel 5.6.15-arch1-1)
 |  In scope on bash 5.0.3 on Debian 10 Buster inside WSL 1
 |   |  In scope on bash 4.3.48 on Ubuntu 16.04.6 LTS
 ↓   ↓   ↓
 no  no  no   define, use
yes yes  no   define and use in same line
yes yes  no   define and use in subshell
yes yes  no   define and use in context
yes yes  no   define and use in && chain
yes yes  no   define in context and use in || chain
yes yes  no   define and use in for loop body
yes yes  no   define and use in while loop head
yes yes  no   define and use in same case
yes yes  no   define in case, use in fall-through
 no  no  no   define and use inside function in same line
 no  no  no   define local and use inside function in same line
 no  no  no   define, use as function argument
yes yes  no   define, use as function argument in same line
yes yes yes   on-the-fly export, use in different shell
 no  no  no   export, use in different shell
 no  no  no   define in command substitution, use in parent in same line
 no  no  no   read from here-string, use in parent in same line
 no  no  no   read from process substitution, use in parent in same line
 no  no  no   read from pipe and use in same line
yes yes  no   define, unrelated read from file, use in same line
yes  no  no   define, unrelated read from process substitution, use in same line
yes yes  no   define, unrelated cat from process substitution, use in same line
 no  no  no   define, unrelated read ... in subshell, use in same line
yes yes  no   define, unrelated read ... in command substitution, use in same line

对于我对这些结果的解释,请参阅此答案开头的 TL;DR。

于 2020-06-04T16:16:58.347 回答
0

从 bash-5.1 开始,我认为唯一安全的方法是永远不要将进程替换存储在变量中,甚至不在同一行中。另一个答案中的脚本为 bash-5.1 中的每一行输出“否”,并且只有立即替换有效,例如paste <(echo 1) <(echo 2).

于 2021-05-02T12:38:40.687 回答