【问题标题】:Bash: What is the scope of the process substitution?Bash:进程替换的范围是什么?
【发布时间】:2018-03-21 11:08:30
【问题描述】:

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

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

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

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

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

我读过这个article,但似乎我的理解是错误的。

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

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

阅读后我的最佳猜测是,创建的 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维护多久,那么进程替换的范围是什么?

【问题讨论】:

    标签: bash process-substitution


    【解决方案1】:

    TL;DR

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

    长篇大论

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

    除了链接的 Bash Hackers Wiki 中的错误 cmets 外,我找不到任何相关文档。甚至bash's manual 也没有谈到进程替换的范围。所以我们一直在做实验(或者阅读bash的源代码,我没有)。

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

    #! /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。

    【讨论】:

      【解决方案2】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-05-09
        • 1970-01-01
        • 1970-01-01
        • 2011-07-22
        • 2011-11-11
        • 2020-10-18
        • 2017-04-22
        相关资源
        最近更新 更多