Bash Coprocesses
A Bash coprocess is a background command that stays alive to exchange messages with the script. The script writes to its input and reads from its output while both keep running.
One message there and back
The script sends one line into the coprocess and reads one line back. The reply proves the message travelled both ways.
coproc { while IFS= read -r line; do echo "got: ${line^^}"; done; }
printf 'hello\n' >&"${COPROC[1]}"
IFS= read -r reply <&"${COPROC[0]}"
echo "$reply"
exec {COPROC[1]}>&-
wait
echo "worker done"Two exchanges with one coprocess
The same coprocess answers twice, once per message. Both sums prove the coprocess stayed alive between exchanges.
coproc { while read -r a b; do echo $((a + b)); done; }
printf '3 4\n' >&"${COPROC[1]}"
IFS= read -r sum <&"${COPROC[0]}"
echo "first sum: $sum"
printf '10 5\n' >&"${COPROC[1]}"
IFS= read -r sum <&"${COPROC[0]}"
echo "second sum: $sum"
exec {COPROC[1]}>&-
waitA coprocess that exits on its own
This coprocess sends exactly three lines and then exits. The script reads exactly three replies, so nothing waits forever.
coproc { printf 'one\ntwo\nthree\n'; }
for i in 1 2 3; do
IFS= read -r word <&"${COPROC[0]}"
echo "word: $word"
done
wait
echo "worker done"Example
coproc { count=0; while IFS= read -r line; do count=$((count + 1)); echo "count: $count"; done; }
for word in apple banana cherry; do
printf '%s\n' "$word" >&"${COPROC[1]}"
IFS= read -r reply <&"${COPROC[0]}"
echo "$reply"
done
exec {COPROC[1]}>&-
wait
echo "counter done"Key points
- coproc: the coproc keyword starts a coprocess and records its file descriptors in the COPROC array.
- COPROC[0]: the script reads the coprocess output from this descriptor.
- COPROC[1]: the script writes the coprocess input to this descriptor.
- Bounded reads: read exactly the replies you expect, so the script never waits on a silent coprocess.
- Clean ending: close the input descriptor so the coprocess sees end of input, then wait for it.
- Default array: a second unnamed coproc replaces the COPROC array, so finish the first coprocess first.
In this example, a coprocess keeps a running count across messages. Each loop turn sends one word and reads back the new count. The counts rise across turns, which proves the coprocess kept its state. Closing the input ends its read loop, and wait collects it.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
After sending lines to a coprocess, why must the script close the coprocess input before wait?