Bash:流程替换的范围是什么?

时间:2017-10-10 06:34:12

标签: bash process-substitution

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

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

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

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

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

我已经阅读了article,但似乎我的理解是错误的。

  

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

     

实质上,扩展到函数内的变量的进程替换保持打开,直到发生进程替换的函数返回 - 即使分配给由函数调用者设置的本地。动态范围并不能保护它们不被关闭。

我最好的猜测是,在阅读之后,创建的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维持多长时间,然后进程替换的范围是什么?

2 个答案:

答案 0 :(得分:3)

TL; DR

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

漫长的故事

我像您一样解释了Bash Hackers Wiki上引用的文章。同样,我得出的结论是,在函数内声明用于进程替换的变量并不能保证它们保持打开状态。在某些系统上,还有许多其他方法可以使它们保持打开状态,尤其是使用command groups时,例如子外壳(...)和上下文{...}。但是,这些技巧在某些系统上仍然失败。

除了链接的Bash Hackers Wiki中的错误注释之外,我找不到任何相关文档。甚至bash's manual都没有提到进程替换的范围。因此,我们只能进行实验(或阅读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。

答案 1 :(得分:0)

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