2

Here's the command:

{ ( echo "to stdout"; echo "to stderr" >&2 ) > >(sleep 1; tee stdout.txt); } 2> >(sleep 2; tee stderr.txt >&2 )

And here's what I see:

zsh

% { ( echo "to stdout"; echo "to stderr" >&2 ) > >(sleep 1; tee stdout.txt); } 2> >(sleep 2; tee stderr.txt >&2 )
to stdout
to stderr
%

bash

$ { ( echo "to stdout"; echo "to stderr" >&2 ) > >(sleep 1; tee stdout.txt); } 2> >(sleep 2; tee stderr.txt >&2 )
$ to stdout
to stderr

Note that bash writes it's prompt $ before output completes, but zsh waits for the substituted processes to complete before prompting the user %.

I suppose a reasonable answer might be "because that's how zsh works", but I want to know if this is documented anywhere so that I can learn more. As a zsh user among many bash users, I try to know the differences so that I know what to expect when I say "hey run this command".

(related question: https://stackoverflow.com/a/53051506/1054322)

1 Answer 1

3

Shorter example that behaves the same way in both shells. The parent shell does not wait.

(sleep 1; echo done) > >(cat >file) &

With the following modification, zsh will wait, but bash won't:

{ (sleep 1; echo done) & } > >(cat >file)

A similar example is given in the zshexpn(1) manual under "PROCESS SUBSTITUTION". There, it mentions that the shell will wait for the asynchronous process within { ... } when redirecting the output of an asynchronous process into a process substitution (which is also asynchronous, in both shells), but will not wait without the { ... }.

The manual phrases the problem as

There is an additional problem with >(process); when this is attached to an external command, the parent shell does not wait for process to finish and hence an immediately following command cannot rely on the results being complete.

By enclosing the asynchronous command in a compound { ... }, the "extra processes here are spawned from the parent shell which will wait for their completion." The way that I interpret this is that the process that we redirect into with >(...) will be spawned from the shell whereas without { ... }, it will be spawned from the job that we start as a background job (and the shell will therefore not wait for it).

An evidence for this is found if you output $ZSH_SUBSHELL inside the process substitution that you redirect into. With the blocking code, it will output 1, while with the non-blocking code, it will output 2, showing that the output redirection is run as a sub-shell inside another sub-shell (which the main shell won't wait for).

The bash shell seems to handle this differently and spawns all background jobs independently of the parent shell in both cases, even though outputting $BASH_SUBSHELL inside the output process substitution exhibits the same behaviour as in zsh.

1
  • I'm in slightly over my head, but anchoring the behavior to a spot in the manual will let me learn the terminology so I can remedy that. This is great, thank you. Commented Dec 21, 2019 at 5:10

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .