使用一点 awk 了解 bash 中的嵌套进程替换

Understanding nested process substitution in bash, with a little awk

我似乎想不通,这里发生了什么。我有 4 个版本的相同代码,唯一的区别是 code-blocks/lines 的顺序。我最初的期望是重定向顺序没有任何区别,但它似乎不正确。我还假设 >() 具有一些文件描述符阴谋的屏蔽特性,但没有...

请不要问我用它做什么,也不需要替代解决方案,我想了解这段代码。否则我对我对过程替代的理解的信念将永远被打破...

元代码:

1_cmd_producing_both_stdout_and_stderr
|
+-stdout-> 2_cmd_producing_both_stdout_and_stderr
|          |
|          +-stdout-> A_awk_writing_stdout_to_file_producing_stderr
|          |
|          +-stderr-> B_awk_writing_stdout_to_file_producing_stderr
|
+-stdout-> 3_cmd_producing_both_stdout_and_stderr
           |
           +-stdout-> C_awk_writing_stdout_to_file_producing_stderr
           |
           +-stderr-> D_awk_writing_stdout_to_file_producing_stderr

版本 1: 1 2 A B 3 C D

版本 2: 1 2 B A 3 D C

版本 3: 1 3 D C 2 B A

版本 4: 1 3 C D 2 A B

注意:

我试过了 2个 和 2 B A,它们也产生一致的输出,类似于版本 1。

awk: GNU Awk 4.1.1,API:1.1(GNU MPFR 3.1.2-p3,GNU MP 6.0.0)

bash: GNU bash,版本 4.3.30(1)-release (x86_64-pc-linux-gnu)

版本 1,这会产生我期望的输出:

( echo log; echo err 1>&2; ) \
    1> >( ( echo -n '1.'; cat; echo '1.ERR' 1>&2 ; ) \
        1> >( awk 'BEGIN { print "error 1" >"/dev/stderr" } { print [=12=] }' >out.out ) \
        2> >( awk 'BEGIN { print "error 2" >"/dev/stderr" } { print [=12=] }' >out.err )
    ) \
    2> >( ( echo -n '2.'; cat; echo '2.ERR' 1>&2 ; ) \
        1> >( awk 'BEGIN { print "error 3" >"/dev/stderr" } { print [=12=] }' >err.out ) \
        2> >( awk 'BEGIN { print "error 4" >"/dev/stderr" } { print [=12=] }' >err.err )
    )

文件内容:

out.out 1.log
out.err 1.ERR
err.out 2.err
err.err 2.ERR

输出:

error 4
error 2
error 1
error 3

版本 2:

注意:与版本 1 相比,第一层与第二层、第三层与第四层的缩进线互换了。

( echo log; echo err 1>&2; ) \
    1> >( ( echo -n '1.'; cat; echo '1.ERR' 1>&2 ; ) \
        2> >( awk 'BEGIN { print "error 1" >"/dev/stderr" } { print [=15=] }' >out.err ) \
        1> >( awk 'BEGIN { print "error 2" >"/dev/stderr" } { print [=15=] }' >out.out )
    ) \
    2> >( ( echo -n '2.'; cat; echo '2.ERR' 1>&2 ; ) \
        2> >( awk 'BEGIN { print "error 3" >"/dev/stderr" } { print [=15=] }' >err.err ) \
        1> >( awk 'BEGIN { print "error 4" >"/dev/stderr" } { print [=15=] }' >err.out )
    )

文件内容:

(!) out.err error 2\n1.ERR
out.out 1.log
(!) err.err 2.ERR\nerror 4
err.out 2.err

输出:

error 3
error 1

版本 3:

注意:与版本 2 相比,第一级缩进代码块已交换。

( echo log; echo err 1>&2; ) \
    2> >( ( echo -n '2.'; cat; echo '2.ERR' 1>&2 ; ) \
        2> >( awk 'BEGIN { print "error 1" >"/dev/stderr" } { print [=18=] }' >err.err ) \
        1> >( awk 'BEGIN { print "error 2" >"/dev/stderr" } { print [=18=] }' >err.out )
    ) \
    1> >( ( echo -n '1.'; cat; echo '1.ERR' 1>&2 ; ) \
        2> >( awk 'BEGIN { print "error 3" >"/dev/stderr" } { print [=18=] }' >out.err ) \
        1> >( awk 'BEGIN { print "error 4" >"/dev/stderr" } { print [=18=] }' >out.out )
    )

文件内容:

(!) err.err error 2\n2.ERR
(!) err.out 2.err\nerror 3
(!) out.err 1.ERR\nerror 4
out.out 1.log

输出:

error 1
(!)

版本 4:

注意:与版本 3 相比,第一层与第二层、第三层与第四层的缩进线互换了。

( echo log; echo err 1>&2; ) \
    2> >( ( echo -n '2.'; cat; echo '2.ERR' 1>&2 ; ) \
        1> >( awk 'BEGIN { print "error 1" >"/dev/stderr" } { print [=21=] }' >err.out ) \
        2> >( awk 'BEGIN { print "error 2" >"/dev/stderr" } { print [=21=] }' >err.err )
    ) \
    1> >( ( echo -n '1.'; cat; echo '1.ERR' 1>&2 ; ) \
        1> >( awk 'BEGIN { print "error 3" >"/dev/stderr" } { print [=21=] }' >out.out ) \
        2> >( awk 'BEGIN { print "error 4" >"/dev/stderr" } { print [=21=] }' >out.err )
    )

文件内容:

(!) err.out 2.err\nerror 4\nerror 3
err.err 2.ERR
out.out 1.log
out.err 1.ERR

输出:

error 2
error 1
(!)

版本 2、3、4 发生了什么??

您认为输出重定向的顺序无关紧要的假设是不正确的。秩序很重要。让我们只考虑代码示例 2 中的字符串 "error 2",看看为什么它被写入文件 out.err。代码是:

( echo log; echo err 1>&2; ) \
    1> >( ( echo -n '1.'; cat; echo '1.ERR' 1>&2 ; ) \
        2> >( awk 'BEGIN { print "error 1" >"/dev/stderr" } { print [=10=] }' >out.err ) \
        1> >( awk 'BEGIN { print "error 2" >"/dev/stderr" } { print [=10=] }' >out.out )
    ) \
    2> >( ( echo -n '2.'; cat; echo '2.ERR' 1>&2 ; ) \
        2> >( awk 'BEGIN { print "error 3" >"/dev/stderr" } { print [=10=] }' >err.err ) \
        1> >( awk 'BEGIN { print "error 4" >"/dev/stderr" } { print [=10=] }' >err.out )
)

字符串"error 2"被进程B写入了它的stderr。由于 shell 解析以启动进程 B 的字符串不包含到 stderr 的重定向,因此进程 B 从其父进程继承其 stderr。它的父级是您标记为 2 的子 shell。该进程有 2 个重定向,每个重定向到子进程。在进程 B 启动时,进程 2 的 stderr 已被定向到进程 A,因此进程 B 将在此处写入字符串。进程 A 读取它并将字符串写入其标准输出,即文件 out.err。如果颠倒重定向的顺序,则进程 2 的标准错误还没有被重定向,因此进程 B 的标准错误将与原始进程的标准错误相同(例如,您的tty)