3

The script below has no other purpose than to illustrate this question.

#!/usr/bin/env zsh

arbitrary_pipeline () {
    shuf | tr a-z A-Z
}

tmpdir=$( mktemp -d )

mkfifo $tmpdir/{orig,alt}

{ tee $tmpdir/orig | arbitrary_pipeline > $tmpdir/alt; } &
pid=$!

paste $tmpdir/orig $tmpdir/alt

rm -rf $tmpdir

wait $pid

The script uses tee and two named pipes to split some (arbitrary) standard input into two streams, redirects one of them to an arbitrary pipeline, and passes the resulting two streams the inputs to paste. Schematically:

STDIN --- > --.- arbitrary_pipeline -.
               \                      \
          paste `----<FIRST-ARGUMENT>  `- <SECOND-ARGUMENT> --> STDOUT

(In this case arbitrary_pipeline just shuffles its stdin, and converts it to uppercase, but, as the name implies, it could be anything.)

The script's stdout output looks fine, but the wait command always fails:

% grep -iP 'z.*s.*h' /usr/share/dict/words | /tmp/test.sh
Nietzsche   CITIZENSHIP'S
Zubeneschamali  NIETZSCHE
Zubeneschamali's    ZUBENESCHAMALI
citizenship CITIZENSHIP
citizenship's   ZUBENESCHAMALI'S
/tmp/test.sh:wait:18: pid 26357 is not a child of this shell

What am I doing wrong?


FWIW:

/usr/bin/env zsh --version
# zsh 5.0.7 (x86_64-pc-linux-gnu)

EDITS:

  1. added curly braces around the tee pipeline, per jordanm's suggestion. (Results did not change, though.)
  2. replaced &! with & in response to Stéphane Chazelas' comment. (Again, results did not change.)
4
  • 1
    Try adding braces: { tee $tmpdir/orig | arbitrary_pipeline > $tmpdir/alt; } & Commented Nov 4, 2016 at 20:05
  • @jordanm: I get the same results Commented Nov 4, 2016 at 20:10
  • &! is explicitely to disown the job! Of course wait will fail, that's what &! is for. Use & instead of &! if you still want the shell to consider the job as its child so you can wait for it. Commented Nov 5, 2016 at 10:07
  • @StéphaneChazelas: I get exactly the same results (at least outwardly) with & alone. (I will edit my post to eliminate my stupid red herring.) Commented Nov 5, 2016 at 11:19

1 Answer 1

3

Before 5.0.8, zsh couldn't wait for already dead jobs. That was changed in 5.0.8 in 2014. See the change there.

Here, you can just redirect stderr to /dev/null to ignore the problem:

wait $pid 2> /dev/null

Note that in:

{ tee $tmpdir/orig | arbitrary_pipeline > $tmpdir/alt; } &

as an optimisation, zsh will not fork an extra process for arbitrary_pipeline, it will execute it in the same process as the one that runs that subshell started in background.

paste will not finish before it sees EOF on its stdin, its stdin being the pipe where $pid (and its children if any) is writing at the other end. So, it will not see eof until $pid (and children) has closed all its file descriptors (typically only stdout) to the writing end of the pipe. Unless $pid does explicitly close its stdout (which is very uncommon), that will only happen when it exits.

What that means is the paste will, in most cases, not exit before $pid, it's still a good idea to do the wait just in case.

Note that here, you could use a coproc to avoid the temporary fifos:

coproc arbitrary_pipeline
cat >&p | paste - /dev/fd/3 3<&p &
coproc : close
wait

(note that wait also waits for the coprocs).

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.