The coproc keyword

:V4:

Syntax

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.

 coproc [NAME] command [redirections]

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.

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()}'; }

redirections

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.

Example

Redirecting the output of a script to a file and to the screen

#!/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

Pitfalls

Avoid the command | while read subshell

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.

Buffering

In the first example, we used fflush() in the awk command, this was done on purpose, as always when you use pipes the io 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 return simply because the output is waiting in a buffer.

See this faq entry http://wooledge.org:8000/BashFAQ/009 for some workarounds.

background processes

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.

Only one coprocess at a time

The title says it all, complain to the bug-bash mailing list if you want more.

syntax/keywords/coproc.txt · Last modified: 2009/02/25 06:26 by thebonsai
www.chimeric.de Creative Commons License Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0