bash コマンドをパイプして Ctrl+C を機能させ続けるにはどうすればよいでしょうか?

bash コマンドをパイプして Ctrl+C を機能させ続けるにはどうすればよいでしょうか?

Ctrl + C を検出してシャットダウンするカスタム コマンド ライン ソフトウェア (例: loopHelloWorld) を検討しています。 答えを失わずにパイプするにはどうすればよいでしょうかCtrl+C?

$ loopHelloWorld
    - Ctrl+C to nicely shutdown

しかしパイプでは、パイプは適切なシャットダウンなしにソフトウェアを終了させます

$ loopHelloWorld | 
    while IFS= read -r line; do
        echo "$line"
    done

ping example.com |
  while IFS= read -r line; do
    echo "$line"
  done

答え1

Ctrl+Cパイプライン内のすべてのプロセスに SIGINT が送信されます (それらはすべて、対話型シェルのフォアグラウンド ジョブに対応する同じプロセス グループで実行されるため)。

つまり:

loopHelloWorld | 
  while IFS= read -r line; do
    echo "$line"
  done

実行中のプロセスloopHelloWorldと、ループを実行するサブシェルを実行しているプロセスの両方whileが を取得しますSIGINT

loopHelloWorldメッセージをstdoutに書き込むとCtrl+C to nicely shutdown、パイプにも書き込まれます。もう一方の端のサブシェルがすでに死んでいる場合はloopHelloWorldまたSIGPIPE を受信し、これを処理する必要があります。

ここでは、そのメッセージはコマンドの通常の出力ではないため (pingただし、この例には適用されません)、stderr に書き込む必要があります。そうすると、パイプを通過しなくなります。

または、while ループを実行しているサブシェルで SIGINT を無視して、loopHelloWorldSIGINT 後の出力を読み取り続けることもできます。

loopHelloWorld | (
  trap '' INT
  while IFS= read -r line; do
    printf '%s\n' "$line"
  done
)

ただし、 を押すとパイプラインの終了ステータスが 0 になりますCtrl+C

この特定の例の別のオプションは、 の代わりにzshまたは を使用することです。これらのシェルでは、ループはメイン シェル プロセスで実行されるため、SIGINT の影響を受けません。ksh93bashwhile

ただし、フォアグラウンド プロセス グループで実行するloopHelloWorld | cat場合には役に立ちません。catloopHelloWorld

答え2

トラップを使用する:

#! /bin/bash

trap handleInt SIGINT

interrupted=0

function handleInt {
    echo "Interrupted..."
    interrupted=1
}


while true
do
    [[ interrupted -ne 0 ]] && { echo "Ctrl-C caught, exiting..." ; exit ; }
    echo "Sleeping..."
    sleep 1
    read -r line
    echo "$line"
done

関連情報