Zoltan K.
Zoltan K.

Reputation: 1158

What is the difference between {} 1>&2 and () 1>&2 in an output-captured function in bash

  1. with {} - execute while loop in the current shell

    function f {
        {
            while : ; do echo -n a; done  &
        } 1>&2
    }
    
    a=$( f ); echo "returned"
    

    -> f() will never return!!!

  2. () execute while loop in a subshell

    function f {
        (
            while : ; do echo -n a; done  &
        ) 1>&2
    }
    
    a=$( f ); echo "returned"
    

    -> f() will return!!!

Why? will one of them return, but not the other? I don't get it...


My analysis:

The while loop will be forked and start its own background process due to the ending & on the while-loop line. This background process inherits the current open fd list.

As far as I understand, since the while loop is encapsulated, it inherits the encapsulation's fd list. This is how

{ echo log; echo err 1>&2; } 1>l_file 2>e_file

works as expected, l_file will contain "log", e_file will contain "err".

So in either the case of {} 1>&2 or () 1>&2, bash is informed that it should expect no stdout to capture.

Why is it blocking on the {} 2>&1 case?


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


EDIT

Based on the answers so far, I did some more analysis:

11) {}

function f {
    {
        while : ; do echo -n a; done  &
        echo "after loop"
    } 1>&2
    echo "end of function"
}

a=$( f ); echo "returned"

-> after loop is displayed

12) ()

function f {
    (
        while : ; do echo -n a; done  &
        echo "after loop"
    ) 1>&2
    echo "end of function"
}

a=$( f ); echo "returned"

-> after loop is displayed

-> returned is displayed

Upvotes: 5

Views: 88

Answers (1)

Charles Duffy
Charles Duffy

Reputation: 295472

A command substitution doesn't return until the FIFO it opens as output is closed.

When you redirect a subshell, that subshell doesn't hold a file descriptor pointing to the original FD; it doesn't have any need to, since the redirection will be implicitly ended by that subshell's termination.

When you redirect a block, the block needs to retain a copy of the original descriptor to restore on exit. Consequently, there will be an automatically-assigned file descriptor storing a copy of the original (pre-redirection) stdout, and the existence of this FD prevents the FIFO from having the write end closed.


Observe the difference:

f() {
  ls -l "/proc/$BASHPID/fd"
}

out1=$( ( f; ) 2>&1; )
out2=$( { f; } 2>&1; )

In the above, out1 may (with irrelevant fields stripped) look like:

0 -> /dev/pts/0
1 -> pipe:[1146313]
2 -> pipe:[1146313]
255 -> /dev/pts/0

...whereas out2 may under similar conditions look like:

0 -> /dev/pts/0
1 -> pipe:[1146327]
10 -> /dev/pts/0
2 -> pipe:[1146327]
255 -> /dev/pts/0

Note the additional FD 10, storing a backup to be restored.

Upvotes: 6

Related Questions