mkfifo(名前付きパイプ)を使う方法もありますが、FIFOファイルの手動管理が必要で、スクリプトが複雑になりがちです。
この記事では、bashのcoproc(コプロセス)コマンドを使った双方向通信設計を解説します。coprocはbashの組み込み機能で、コプロセスとの読み書き用ファイルディスクリプタ(FD)を自動的に作成・管理するため、mkfifoより簡潔な構文で永続プロセスへの繰り返し入力が実現できます。RHEL 9系/Ubuntu 24.04 LTSを想定環境として説明します。
この記事のポイント
・ coproc NAMEでbashが双方向パイプFDを自動管理し、外部コマンドが不要
・ ${NAME[0]}が読み取りFD、${NAME[1]}が書き込みFDとして使える
・ 永続プロセスへの繰り返し入力でbcやsqlite3の起動コストを削減できる
・ バッファリング問題はstdbuf -oLで解消できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜcoproc(コプロセス)が必要なのか
シェルスクリプトから外部プログラムへ値を繰り返し送る場面を考えます。たとえば、100件の浮動小数点計算をbcで実行する場合、次のような書き方だとループのたびにbcが起動・終了します。# 毎回bcを起動し直す書き方 for i in $(seq 1 100); do result=$(echo "$i * 3.14159" | bc -l) echo "$i * pi = $result" done
この問題の解決策は3つあります。
| 手法 | 特徴 | 難易度 |
|---|---|---|
| パイプで都度起動 | シンプルだが毎回プロセス起動 | 低 |
| mkfifoで名前付きパイプ | FIFOファイルの手動管理が必要 | 中 |
| coproc(コプロセス) | bash組み込みでFD管理を自動化 | 中 |
coprocの基本構文と動作
1. 名前付きコプロセスの宣言
coprocの基本構文は次のとおりです。# 複合コマンドとして宣言する書き方(推奨) coproc NAME { command; } # 単純なコマンドの場合 coproc NAME command
・ ${NAME[0]}:コプロセスの標準出力からの読み取りFD
・ ${NAME[1]}:コプロセスの標準入力への書き込みFD
・ $NAME_PID:コプロセスのプロセスID
コプロセスはバックグラウンドで動作し続けます。スクリプトがFDを通じて値をやり取りする間、コプロセスは常に起動したままです。
2. FDを使った読み書きの基本
coprocのFDを使う基本パターンを示します。# コプロセスを起動(-l は数学ライブラリを有効化) coproc BC { bc -l; } # 式を書き込む: コプロセスの標準入力へリダイレクト printf '10 * 20\n' >&${BC[1]} # 結果を読み取る: コプロセスの標準出力からリダイレクト read result <&${BC[0]} echo "10 * 20 = $result" printf 'sqrt(2)\n' >&${BC[1]} read result <&${BC[0]} echo "sqrt(2) = $result"
10 * 20 = 200 sqrt(2) = 1.41421356237309504880
>&${BC[1]} で書き込みFDへリダイレクトし、<&${BC[0]} で読み取りFDからリダイレクトします。bashがFD番号を管理するため、実際の数値(3や4など)は意識しなくてよい点もcoprocの利点です。
3. コプロセスの終了処理
コプロセスを終了するには、書き込みFDを閉じてからwaitで回収します。# 書き込みFDを閉じる(コプロセスのstdinがEOFになり終了する) exec {BC[1]}>&- # コプロセスの終了を待つ wait $BC_PID
実装パターン|bcを使った繰り返し算術計算
coprocを使った永続bcセッションの実装例を示します。ループを通じて同じbcセッションを維持することで、100件の計算をbcの起動1回で処理できます。#!/bin/bash # 後片付けを確実にするtrapを設定 cleanup() { exec {BC[1]}>&- 2>/dev/null wait "$BC_PID" 2>/dev/null } trap cleanup EXIT # 永続bcセッションをcoprocで起動 coproc BC { bc -l; } echo "--- 1つのbcセッションで繰り返し計算 ---" for angle in 0 30 45 60 90; do # degree を radian に変換してsin値を計算 printf 's(%s * 3.14159265358979 / 180)\n' "$angle" >&${BC[1]} read sin_val <&${BC[0]} printf "sin(%3d度) = %s\n" "$angle" "$sin_val" done
--- 1つのbcセッションで繰り返し計算 --- sin( 0度) = 0 sin( 30度) = .49999999999999999991 sin( 45度) = .70710678118654752440 sin( 60度) = .86602540378443864676 sin( 90度) = 1.00000000000000000000
シェルスクリプトの実装設計をより体系的に学びたい方は、シェルスクリプト実践講座もあわせて参考にしてみてください。
バッファリング問題とstdbuf
なぜバッファリングが問題になるのか
bcは行バッファリングで動作するため、先ほどの例のようにreadが正常に結果を受け取れます。しかし、sqlite3など多くのプログラムはパイプに接続されるとブロックバッファリングに切り替わります。ブロックバッファリングでは出力がバッファいっぱいになるまでフラッシュされないため、readがいつまでも応答を受け取れずにハングします。stdbuf -oL で行バッファリングに変更する
stdbuf -oL でコプロセスを起動すると、標準出力を行バッファリングに設定できます。
# stdbuf -oL でsqlite3の標準出力を行バッファリングに設定 coproc SQL { stdbuf -oL sqlite3 "$DB_PATH"; } # クエリを送信してデリミタトークンを追加 printf 'SELECT name FROM users LIMIT 3; SELECT "END_OF_RESULTS";\n' >&${SQL[1]} # "END_OF_RESULTS"が来るまで結果を読み取る while IFS= read -r line <&${SQL[0]}; do [[ "$line" == "END_OF_RESULTS" ]] && break echo "row: $line" done
stdbufが使えない環境では、unbuffer(expectパッケージ)が代替として使えます。
トラブルシュート
1. readでハングする場合
readが応答を待ち続けてハングする場合、コプロセスの出力がバッファリングされています。・ 対処1:
stdbuf -oL でコプロセスを起動してバッファをフラッシュさせる・ 対処2:
read -t 秒数 でreadにタイムアウトを設定して強制抜け出しを入れる・ 対処3:コプロセス側のコマンドが改行で終わる出力を出しているかを確認する
2. コプロセスが意図せず終了する場合
コプロセスが途中で終了すると、書き込みFDへの送信でエラーが発生します。・ 確認方法:
kill -0 $BC_PID 2>/dev/null で生死を確認する(終了コード0が起動中)・ 主な原因:コプロセスへの不正な入力(bcなら未対応の構文)、パイプのFDがすでに閉じられている
・ 対処:コプロセスの起動を関数化し、エラー時に再起動できる構造にする
3. bash 4.0未満では使えない
coprocはbash 4.0以降の機能です。#!/bin/bash と明示してください。macOSの標準bashはbash 3.2のため、HomebrewでBash 5系をインストールする必要があります。シェルのバージョンは bash --version で確認できます。
本記事のまとめ
bashのcoproc(コプロセス)を使った双方向通信設計のポイントをまとめます。| やりたいこと | coprocの書き方 |
|---|---|
| コプロセスを起動する | coproc NAME { cmd; } |
| コプロセスへ書き込む | printf '...\n' >&${NAME[1]} |
| コプロセスから読み取る | read var <&${NAME[0]} |
| コプロセスのPIDを取得する | $NAME_PID |
| コプロセスを終了させる | exec {NAME[1]}>&-; wait $NAME_PID |
| バッファリング問題を解消する | coproc NAME { stdbuf -oL cmd; } |
・ mkfifoと比べてFIFOファイルの手動管理が不要で、スクリプトがシンプルになる
・ 永続プロセスへの繰り返し入力で起動コストを削減でき、bcやsqlite3の活用に有効
・ バッファリング問題はstdbuf -oLで対応でき、readのハングを防げる
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:シェルスクリプトを実行場所に依存しない設計にする方法|dirname・pushdとpopdで相対パスの罠を回避する実装パターン
- この記事の属するカテゴリ:シェルスクリプトへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます