coproc [NAME] command [redirections]
Bash 4.0 introduced the coprocesses, a feature certainly familiar to ksh users.
coproc starts a command in the backgound setting up pipes so that you can
interact with it. Optionally, the co-process can have a name NAME.
If NAME is given, the following command must be a compound command. If no NAME ist given, the command can be a simple command or a compound command.
The redirections are normal redirections that are set after the pipe has been set up, some examples:
# redirecting stderr in the pipe
$ coproc { ls thisfiledoesntexist; read ;} 2>&1
[2] 23084
$ read -u ${COPROC[0]};printf "%s\n" "$REPLY"
ls: cannot access thisfiledoesntexist: No such file or directory
#let the output of the coprocess go to stdout
$ { coproc mycoproc { awk '{print "foo" $0;fflush()}' ;} >&3 ;} 3>&1
[2] 23092
$ echo bar >&${mycoproc[1]}
$ foobar
Here we need to save the previous file descriptor of stdout, because by the time we want to redirect the fds of the coprocess stdout has been redirected to the pipe.
The traditional KSH workaround to avoid the subshell when doing command | while read is to use a coprocess, unfortunately, it seems that bash's behaviour differ from KSH.
In KSH you would do:
ls |& #start a coprocess while read -p file;do echo "$file";done #read its output
In bash:
#DOESN'T WORK
$ coproc ls
[1] 23232
$ while read -u ${COPROC[0]} line;do echo "$line";done
bash: read: line: invalid file descriptor specification
[1]+ Done coproc COPROC ls
By the time we start reading from the output of the coprocess, the file descriptor has been closed.
In the first example, we used fflush() in the awk command, this was
done on purpose, as always when you use pipes the I/O operations are buffered,
let's see what happens with sed:
$ coproc sed s/^/foo/
[1] 22981
$ echo bar >&${COPROC[1]}
$ read -t 3 -u ${COPROC[0]}; (( $? >127 )) && echo "nothing read"
nothing read
Even though this example is the same as the first awk example, the
read doesn't return, simply because the output is waiting in a buffer.
See this faq entry on Greg's wiki for some workarounds.
The file descriptors of the coprocesses are available to the
shell where you run coproc, but they are not inherited.
Here a not so meaningful illustration, suppose we want something that
continuely reads the output of our coprocess and echo the result:
#NOT WORKING
$ coproc awk '{print "foo" $0;fflush()}'
[2] 23100
$ while read -u ${COPROC[0]};do echo "$REPLY";done &
[3] 23104
$ ./bash: line 243: read: 61: invalid file descriptor: Bad file
descriptor
it fails, because the descriptor is not avalaible in the subshell
created by &.
A possible workaround:
#WARNING: for illustration purpose ONLY
# this is not the way to make the coprocess print its output
# to stdout, see the redirections above.
$ coproc awk '{print "foo" $0;fflush()}'
[2] 23109
$ exec 3<&${COPROC[0]}
$ while read -u 3;do echo "$REPLY";done &
[3] 23110
$ echo bar >&${COPROC[1]}
$ foobar
Here the fd 3 is inherited.
The title says it all, complain to the bug-bash mailing list if you want more.
First let's see an example without NAME:
$ coproc awk '{print "foo" $0;fflush()}'
[1] 22978
The command starts in the background, coproc returns immedately. 2 new files descriptors are now available via the COPROC array, We can send data to our command:
$ echo bar >&${COPROC[1]}
And then read its output:
$ read -u ${COPROC[0]};printf "%s\n" "$REPLY"
foobar
When we don't need our command anymore, we can kill it via its pid:
$ kill $COPROC_PID
$
[1]+ Terminated coproc COPROC awk '{print "foo" $0;fflush()}'
Using a named coprocess is as simple, we just need a compound command like when defining a function:
$ coproc mycoproc { awk '{print "foo" $0;fflush()}' ;}
[1] 23058
$ echo bar >&${mycoproc[1]}
$ read -u ${mycoproc[0]};printf "%s\n" "$REPLY"
foobar
$ kill $mycoproc_PID
$
[1]+ Terminated coproc mycoproc { awk '{print "foo" $0;fflush()}'; }
#!/bin/bash
# we start tee in the background
# redirecting its output to the stdout of the script
{ coproc tee { tee logfile ;} >&3 ;} 3>&1
# we redirect stding and stdout of the script to our coprocess
exec >&${tee[1]} 2>&1
coproc keyword is not specified by POSIX(R)coproc keyword appeared in Bash version 4.0-alpha
Discussion
Can you do a coprocess using only regular shell file descriptors? or perhaps using named pipes.
mknod ls_output ls > ls_output &
while read line; do
done < ls_output
Depending on your specific needs there is of course the possibility to use
Coprocesses as described here are just a very easy to use solution for those tasks. It's simple to setup, use and terminate a coprocess.
Seems to me it is about just a complex as using temporary named pipes.
Of course most difficulties with handling a coprocess is often the handling of the data streams especially if you want more than just stdin/stdout.
Since my initial feedback I have written what I would hope is the start of a guide to using co-processes. And yes it can be very much worth the effort.
http://www.ict.griffith.edu.au/anthony/info/shell/co-processes.hints Feedback welcome Anthony Thyssen A.Thyssen@griffith.edu.au
Wow... excellent knowledge collection. I crosslinked it here