5

The intent of the test script1 below is to start an "outer" coprocess (running seq 3), read from this coprocess in a while-loop, and for each line read, print a line identifying the current iteration of the outer loop, start an "inner" coprocess (also running seq, with new arguments), read from this inner coprocess in a nested while loop, and then clean up this inner coprocess. The nested while loop prints some output for each line it reads from the inner coprocess.

#!/bin/bash
# filename: coproctest.sh
PATH=/bin:/usr/bin

coproc OUTER { seq 3; }
SAVED_OUTER_PID="${OUTER_PID}"

exec {OUTER_READER}<&"${OUTER[0]}"
while IFS= read -r -u "${OUTER_READER}" OUTER_INDEX; do

    printf -- '%d\n' "${OUTER_INDEX}"

    START=$(( OUTER_INDEX * 1000000 ))
    FINISH=$(( START + OUTER_INDEX ))

    # (
      coproc INNER { seq "${START}" "${FINISH}"; }
      SAVED_INNER_PID="${INNER_PID}"
      exec {INNER_READER}<&"{INNER[0]}"

      while IFS= read -r -u "${INNER_READER}" INNER_INDEX; do
          printf -- '    %d\n' "${INNER_INDEX}"
      done

      exec {INNER_READER}<&-

      wait "${SAVED_INNER_PID}"
    # )

done
exec {OUTER_READER}<&-
wait "${SAVED_OUTER_PID}"

When I run this script, this is the output I get:

% ./coproctest.sh
1
./coproctest.sh: line 30: warning: execute_coproc: coproc [12523:OUTER] still exists
./coproctest.sh: line 19: INNER_READER: ambiguous redirect
./coproctest.sh: line 21: read: : invalid file descriptor specification
./coproctest.sh: line 25: INNER_READER: ambiguous redirect
2
./coproctest.sh: line 19: INNER_READER: ambiguous redirect
./coproctest.sh: line 21: read: : invalid file descriptor specification
./coproctest.sh: line 25: INNER_READER: ambiguous redirect
3
./coproctest.sh: line 19: INNER_READER: ambiguous redirect
./coproctest.sh: line 21: read: : invalid file descriptor specification
./coproctest.sh: line 25: INNER_READER: ambiguous redirect

I get pretty much the same output if I uncomment the two commented lines.


Q1: Is it possible to have multiple coprocesses running at the same time?

Q2: If so, how should the script above be modified to achieve the desired output?


1 I've only recently started to work with coprocesses, and there is still a lot I don't understand. As a result, this script almost certainly contains incorrect, awkward, or unnecessary code. Please feel free to comment on and/or fix these weaknesses in your responses.

Jeff Schaller
  • 66,199
  • 35
  • 114
  • 250
kjo
  • 14,779
  • 25
  • 69
  • 109

2 Answers2

6

From the "BUGS" section at the very end of the bash manual:

There may be only one active coprocess at a time.

Kusalananda
  • 320,670
  • 36
  • 633
  • 936
2

Q1: Is it possible to have multiple coprocesses running at the same time?

On Bash v4 and above (including current v5), officially no, as noted by @Kusalananda.

However, I can tell you that it might work despite the warning, but of course no guarantee and YMMV. See here for a bit more insight.

Q2: If so, how should the script above be modified to achieve the desired output?

It might (as per above) work just fine once you fix the:

exec {INNER_READER}<&"{INNER[0]}"  # <-- lacks the '$' sign for the 'INNER[0]' variable

which causes the:

./coproctest.sh: line 19: INNER_READER: ambiguous redirect

message, and consequently also the:

./coproctest.sh: line 21: read: : invalid file descriptor specification

message.

It works for me, once fixed that and warning aside.

As for other notes:

  • There's no need to duplicate a coproc's file-descriptors, unless you want to pass them to a child process (a sub-shell or a command or script)
  • You probably did so because the seq command naturally finished quickly, and thus the automatic variables disappeared before you could use them. By doing the way you did, those duplicate file-descriptors will be inherited by all subsequent commands and background processes, which might be undesirable if your co-process actually uses its input pipe and waits for it to be closed in order to exit. So another approach to address that is by a synchronization mechanism, like for instance make your OUTER co-process be { seq 3; exec >&-; read; }, and then when you've consumed its input from the main script do e.g. echo >&${OUTER[1]}; wait "${OUTER_PID}" to let the co-process's read proceed then wait for it. Note that it is not guaranteed that the wait will execute before the $OUTER_PID variable disappears: in such case you can just mute the warning message (or ignore it altogether) and perhaps force a success status with an || true
  • As a personal side-note I can tell you that, if you are really determined in having multiple co-processes simultaneously, you might re-implement a rough equivalent of coproc using Bash v3 syntax over background processes plus mkfifo named-FIFOs, and on Linux also with a trick with Process Substitutions instead of mkfifos. With Bash v4 syntax it can be less complicated, but still a challenging exercise.
LL3
  • 5,270
  • 7
  • 20