Overview
bash 4 から coprocess があるのを知ったので試してみます。
ざっくり言うと、メインプロセスと stdin/stdout を繋ぎながら、バックグラウンドでサブプロセスを起動できる機能です。
イメージ的には cmd &
に近いですが、入出力が繋がってるのがイケてるポイントです。
具体的には、 coproc
で起動すると、メインプロセス側の配列変数にファイルディスクリプタが割り当てられて、それがサブプロセスの stdin/stdout に繋がります。
+-- ${COPROC[1]} --> stdin
+ ↓
[main process] --+ [sub process &]
+ ↓
+-- ${COPROC[0]} <-- stdout
cf.
詳しい使い方とかはこの辺見てください。
https://qiita.com/angel_p_57/items/7b54493e6e66aea01437
https://www.gnu.org/software/bash/manual/html_node/Coprocesses.html
https://unix.stackexchange.com/questions/86270/how-do-you-use-the-command-coproc-in-various-shells
あと bash と ksh zsh とかだと挙動が微妙に違うみたいです。
bash の縛りがきついw
usage
具体的に見た方がたぶん理解が早いので、
1000行の tsv を while read
で 1行ずつ csv に変換する場合
を考えてみます。
$ echo $BASH_VERSION
5.0.17(1)-release
なお、サブプロセスの出力がバッファリングされると詰まるので、awk で試してみます。
1行ずつ処理する際のパフォーマンスを確認したいので、awk の stdin に直接流し込むとかは今は忘れてください。
coproc を使わないケース
1行ずつ読むたびに毎回 awk を起動するので、
#!/bin/bash
seq -f '%04g' 3000 | paste - - - \
| while read x; do
echo "$x" | awk -v OFS=, '{$1=$1; print $0; }'
done
けっこう時間がかかりますね。
$ time bash nouse-coproc.sh >/dev/null
real 0m2.685s
user 0m2.250s
sys 0m0.722s
coproc を使うと
#!/bin/bash
coproc hoge { awk -v OFS=, '{ $1=$1; print $0; fflush(); }' ; }
while read x; do
echo "$x" >&${hoge[1]}
read -u ${hoge[0]} a
echo "$a"
done < <(seq -f '%04g' 3000 | paste - - -)
めっちゃ速くなります。
$ time bash use-coproc.sh >/dev/null
real 0m0.079s
user 0m0.054s
sys 0m0.023s
なんで速くなるか
もう上に答え書いちゃったんですけど、
1行ずつ毎回プロセスを起動するときのオーバヘッドが遅くなる原因です。
このオーバヘッドは入力行数が増えれば増えるほど線形に増大していきます。
それに対して、 coproc は最初に1つだけプロセスを起動して、
1行ずつ stdin に流し込んで、処理結果を stdout から read していくので、
普通に stdin からコマンドに流し込むのと同じ効果があります。
シェル芸で1行ずつ処理する場合に捗りそうですね。
appendix
ファイルディスクリプタがプロセスをまたいで引き継げない問題
たぶん bash 縛りだと思うんでうけど、
サブプロセスにはファイルディスクリプタが引き継げず、直で fd 値を指定しても 不正なファイル記述子です みたいなエラーになってしまいます。
なので seq | while read x; do ...; done
でやろうとすると、パイプの先は別プロセスなので、残念ながら動きません。
代わりに while read x; do ... ; done < <(seq)
のプロセス置換でやったのはこれが理由です。
バッファリングについて
今回は awk の fflush() で挙動を確認しましたが、 stdbuf を使えばコマンドのバッファリングを切り替えられるみたいです。
https://linuxjm.osdn.jp/html/GNU_coreutils/man1/stdbuf.1.html
$ coproc hoge { tr $'\t' , ; }
$ coproc hoge { stdbuf -oL tr $'\t' , ; }
おわり。