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

Jae*_*Lee 5 bash process-substitution

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

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

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

$ ls -al
$ cat <(ls -al)
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

我最好的猜测是,在阅读之后,创建的 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
Run Code Online (Sandbox Code Playgroud)

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

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

Soc*_*owi 6

TL;DR

There seems to be no documentation and therefore no guarantee on the scope of process substitution <(...). I assume the only safe way to keep process substitutions in scope is to define them directly as arguments cmd <(...), on-the-fly exported variables VAR=<(...) cmd, or redirection cmd < <(...). Process substitution defined in this manner remains in scope while cmd is running.

The Long Story

I interpreted the quoted article from Bash Hackers Wiki like you did. Likewise, I came to the same conclusion that declaring variables for process substitution inside a function does not guarantee that they stay open. On some systems there are many other ways to keep them open, especially with command groups like subshells (...) and contextes {...}. However, these tricks still fail on some systems.

I could not find any documentation of this apart from the wrong comments in the linked Bash Hackers Wiki. Even bash's manual does not talk about the scope of process substitution. So we are stuck with experimenting (or reading bash's source code, which I did not).

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

#! /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}'
Run Code Online (Sandbox Code Playgroud)

这些是我的系统的(漂亮的)输出

#! /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}'
Run Code Online (Sandbox Code Playgroud)

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