暗无天日

=============>DarkSun的个人博客

小议bash中的COPROC

在bash中,两个进程之间要传输数据的最常见方法就是用匿名管道了,像这样

commandA|commandB|commandC

但是使用匿名管道最大的一个问题是,数据的传输是单向的,即commandB只能从commandA的输出中读取数据,然后commandB的输出只能被commandC读取。

那么若想要两个进程之间相互读取数据怎么办呢?也就是说我希望上面的commandA和commandC其实是同一个进程怎么办? 一个常见的解决方法就是使用命名管道:

mkfifo in out
trap 'rm in out' EXIT
cmd <in >out &
exec 3> in 4< out
echo $data >&3
read $data <&4

不过使用命名管道也有一个麻烦事,就是需要记得清理生成的管道文件。

不过,自bash4.0开始,bash引入了一个关键字 coproc, 用来在后台创建一个协作进程(co-process), 同时将它的输入和输出通过管道与文件句柄相连。

有点类似于C中的 FILE * popen ( const char * command , const char * type ); 函数,只不过它同时产生输入和输出两个句柄。

COPROC语法

根据bash manual的说法,coprocess的语法为

coproc [NAME] command [redirections]

其中当command为简单命令时,不能有 NAME.

这个说法很容易让人感到困惑,什么叫简单命令?这个概念不直观。

直观点说,可以认为 coproc 有两类用法,一类是基本语法,一类是扩展的语法。

COPROC基本语法

COPROC的基本语法适用于当只需要有一个coprocess的情况,它的语法为

coproc cmd [redirections]

这时与coprocess的输入/输出管道相连的句柄保存在 $COPROC 数组中,分别为 ${COPROC[1]}${COPROC[0]}

因此,你可以使用 echo $data >&"${COPROC[1]}" 来往coprocess中输入数据, 通过使用 echo $data <&"${COPROC[0]}" 来读取coprocess的输出数据。

COPROC的扩展语法

当需要创建多个coprocess时,你就需要使用coproc的扩展语法了,因为它允许你为coprocess命名。

coproc NAME {cmds} [redirections]

有没有觉得它跟bash中定义函数的语法 function NAME {cmds} 很类似?

这时与coprocess的输入/输出管道相连的句柄保存在 $NAME 数组中,分别为 ${NAME[1]}${NAME[0]}

对应的,你可以使用 echo $data >&"${NAME[1]}" 来往coprocess中输入数据, 通过使用 echo $data <&"${NAME[0]}" 来读取coprocess的输出数据。

关闭不需要的管道

若coprocess只需要输入句柄或输出句柄,则可以使用 exec 来关闭不需要的文件句柄

exec {NAME[0]}>&-
exec {NAME[1]}>&-

注意事项

使用coprocess虽然方便,但要当心coprocess由于输出缓存而导致的卡死。

比如下面这个例子

coproc tr a b
echo a >&"${COPROC[1]}"
read var<&"${COPROC[0]}"

你的期望是第三句能够读出字符 b 作为 var 的值, 然而实际上执行到第三句话时会卡死。 这是因为 tr 命令缓存了输出的内容,而并未将其写到终端上来。

因此创建coprocess时一定小心,只能使用那些不会缓存输出的命令。 也正因为此,coprocess的使用范围其实也很受限,真要用来跟其他进程做交互的话,还是推荐使用 expert 比较好。