Understanding nested process substitution in bash, with a little awk

Viewed 288

I can't seem to figure out, what happens here. I have 4 versions of the same code, the only difference is the order of the code-blocks/lines. My initial expectation was that the redirection order does not make any difference, but it does not seem to be correct. I have also assumed that the >() has some shielding properties from file-descriptor machinations, but no...

Please don't ask what am I using it for, nor do I need alternative solutions, I want to understand THIS piece of code. Or my belief in my understanding of process substitution will be broken forever...

Metacode:

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

Version 1: 1 2 A B 3 C D

Version 2: 1 2 B A 3 D C

Version 3: 1 3 D C 2 B A

Version 4: 1 3 C D 2 A B

NOTE:

I tried 2 A B and 2 B A, as well, they produce consistent outputs, similar to Version 1.

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

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

Version 1, this produces the output I expect:

( 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 $0 }' >out.out ) \
        2> >( awk 'BEGIN { print "error 2" >"/dev/stderr" } { print $0 }' >out.err )
    ) \
    2> >( ( echo -n '2.'; cat; echo '2.ERR' 1>&2 ; ) \
        1> >( awk 'BEGIN { print "error 3" >"/dev/stderr" } { print $0 }' >err.out ) \
        2> >( awk 'BEGIN { print "error 4" >"/dev/stderr" } { print $0 }' >err.err )
    )

files contents:

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

output:

error 4
error 2
error 1
error 3

Version 2:

NOTE: The 1st with the 2nd, the 3rd with the 4th second level indented lines are exchanged, compared to Version 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 $0 }' >out.err ) \
        1> >( awk 'BEGIN { print "error 2" >"/dev/stderr" } { print $0 }' >out.out )
    ) \
    2> >( ( echo -n '2.'; cat; echo '2.ERR' 1>&2 ; ) \
        2> >( awk 'BEGIN { print "error 3" >"/dev/stderr" } { print $0 }' >err.err ) \
        1> >( awk 'BEGIN { print "error 4" >"/dev/stderr" } { print $0 }' >err.out )
    )

file contents:

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

output:

error 3
error 1

Version 3:

NOTE: The first level indented code-blocks are exchanged, compared to Version 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 $0 }' >err.err ) \
        1> >( awk 'BEGIN { print "error 2" >"/dev/stderr" } { print $0 }' >err.out )
    ) \
    1> >( ( echo -n '1.'; cat; echo '1.ERR' 1>&2 ; ) \
        2> >( awk 'BEGIN { print "error 3" >"/dev/stderr" } { print $0 }' >out.err ) \
        1> >( awk 'BEGIN { print "error 4" >"/dev/stderr" } { print $0 }' >out.out )
    )

file contents:

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

output:

error 1
(!)

Version 4:

NOTE: The 1st with the 2nd, the 3rd with the 4th second level indented lines are exchanged, compared to Version 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 $0 }' >err.out ) \
        2> >( awk 'BEGIN { print "error 2" >"/dev/stderr" } { print $0 }' >err.err )
    ) \
    1> >( ( echo -n '1.'; cat; echo '1.ERR' 1>&2 ; ) \
        1> >( awk 'BEGIN { print "error 3" >"/dev/stderr" } { print $0 }' >out.out ) \
        2> >( awk 'BEGIN { print "error 4" >"/dev/stderr" } { print $0 }' >out.err )
    )

file contents:

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

output:

error 2
error 1
(!)

What is happening in Version 2, 3, 4??

1 Answers
Related