Ctrl + Cを使用してbashスクリプトを停止することはできません。

Ctrl + Cを使用してbashスクリプトを停止することはできません。

私はリモートシステムに日付とpingを印刷するループを含む単純なbashスクリプトを書いています。

#!/bin/bash
while true; do
    #     *** DATE: Thu Sep 17 10:17:50 CEST 2015  ***
    echo -e "\n*** DATE:" `date` " ***";
    echo "********************************************"
    ping -c5 $1;
done

端末で実行するとCtrl+C^C端末に送信するようですが、スクリプトは停止しません。

MacAir:~ tomas$ ping-tester.bash www.google.com

*** DATE: Thu Sep 17 23:58:42 CEST 2015  ***
********************************************
PING www.google.com (216.58.211.228): 56 data bytes
64 bytes from 216.58.211.228: icmp_seq=0 ttl=55 time=39.195 ms
64 bytes from 216.58.211.228: icmp_seq=1 ttl=55 time=37.759 ms
^C                                                          <= That is Ctrl+C press
--- www.google.com ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 40.887/59.699/78.510/18.812 ms

*** DATE: Thu Sep 17 23:58:48 CEST 2015  ***
********************************************
PING www.google.com (216.58.211.196): 56 data bytes
64 bytes from 216.58.211.196: icmp_seq=0 ttl=55 time=37.460 ms
64 bytes from 216.58.211.196: icmp_seq=1 ttl=55 time=37.371 ms

何回押すか、どれだけ早く押すかは関係ありません。止められない。
自分でテストして実装してみてください。

Ctrl+Z私はそれを停止して停止する追加のソリューションとして使用しましたkill %1

ここで何が起こっているのでしょうか^C

答え1

何が起こるかはSIGINTbashです。pingbashいいえ対話型、両方ともスクリプトを実行する対話型シェルによって生成され、端末の前景プロセスグループとして設定された同じプロセスグループpingで実行されます。bash

ただし、bash対応するSIGINTは、現在実行中のコマンドが終了した後にのみ非同期的に処理されます。bash現在実行中のコマンドがSIGINTによって終了した場合(つまり、終了状況がSIGINTによって終了したことを示す)、SIGINTが受信されたときにのみ終了します。

$ bash -c 'sh -c "trap exit\ 0 INT; sleep 10; :"; echo here'
^Chere

上でbashCtrl-C を押すと SIGINT が受信されますが、sh正常に 0 終了コードで終了するため、SIGINT は無視されるので「ここ」が表示されます。sleepshbash

ping、少なくともiputilsが動作する方法です。中断すると、統計情報が印刷され、ping 応答の有無によって終了ステータス 0 または 1 で終了します。したがって、ping実行中にCtrl-Cを押すとSIGINTハンドラがbash押されましたが、正常に終了するため終了しないことがわかります。Ctrl-Cpingbash

sleep 1そのループに1つを追加し、実行中Ctrl-Cに押すとSIGINTに特別なハンドラがないため、SIGINTで死んでSIGINTで死んだと報告します。この場合、終了します(実際にはSIGINTで自己終了します。順番に)、親エントリに割り込みを報告します。)sleepsleepbashbash

この動作が発生する理由はbash不明であり、この動作が常に決定的ではないことがわかりました。ただ尋ねたbash開発メーリングリストに関する質問修正する:@Jillesは今理由を確認しました。彼の答え)。

同様の動作を発見した唯一の他のシェルはksh93(アップデート、@Jillesが述べたように、FreeBSDでも同様です。sh)。そこで、SIGINTは目立って無視されるようです。ksh93コマンドがSIGINTによって終了されるたびに終了します。

上記と同じ動作を行いますbashが、次の結果も表示されます。

ksh -c 'sh -c "kill -INT \$\$"; echo test'

「テスト」は出力されません。つまり、待機中のコマンドがSIGINTで終了すると、そのSIGINT自体を受信できなくてもSIGINTで自己終了して終了します。

解決策は、以下を追加することです。

trap 'exit 130' INT

スクリプトの上部でbashSIGINT を受け取ると強制終了します。 SIGINTは、いかなる場合でも同期的に処理されず、現在実行中のコマンドが終了した後にのみ処理されます。

理想的にはSIGINTで死亡したことを親に報告したいと思います。したがって、他のbashスクリプトの場合、そのbashスクリプトも中断されます。 anを実行することはexit 130SIGINTで死ぬのと同じではありませんが(一部のシェルは両方の場合に$?同じ値を設定しますが)、SIGINTが死ぬことを報告するためによく使用されます(SIGINTが2のシステムではこれが最も多いです)。

しかし、FreeBSDbashksh93FreeBSDではshこれは機能しません。 130 終了ステータスは SIGINT によって終了として処理されず、親スクリプトはいいえそこを止めなさい。

したがって、おそらくより良いオプションは、SIGINTを受け取った後にSIGINTで自殺することです。

trap '
  trap - INT # restore default INT handler
  kill -s INT "$$"
' INT

答え2

説明は、bashがSIGINTとSIGQUITに対してWCE(待機および協力終了)を実装していることです。http://www.cons.org/cracauer/sigint.html。これは、プロセスが終了するのを待っている間にbashがSIGINTまたはSIGQUITを受信すると、プロセスが終了するのを待ち、プロセスがその信号に従って終了するとそれ自体が終了することを意味します。これにより、ユーザーインターフェイスでSIGINTまたはSIGQUITを使用するプログラムが期待どおりに機能します(信号によってプログラムが終了しない場合、スクリプトは正常に継続します)。

SIGINTまたはSIGQUITをキャプチャした後、結果的にシャットダウンされますが、シグナルを独自に再送信するのではなく、通常のシャットダウン()を使用するプログラムでは欠点が発生します。そのようなプログラムを呼び出すスクリプトは中断できません。本当の解決策はpingやping6のようなプログラムだと思います。

同様の動作がksh93とFreeBSDの/ bin / shで実装されていますが、他のほとんどのシェルでは実装されていません。

答え3

端末はcontrol-cを認識し、新しいフォアグラウンドプロセスグループがまだ作成されていないINTため、フォアグラウンドプロセスグループ(この場合はシェルを含む)にシグナルを送信します。pingこれはトラッピングで簡単に確認できますINT

#! /bin/bash
trap 'echo oh, I am slain; exit' INT
while true; do
  ping -c5 127.0.0.1
done

実行中のコマンドが新しいフォアグラウンドプロセスグループを作成すると、control-cはシェルの代わりにそのプロセスグループに移動します。この場合、端末はシグナルをエクスポートしないため、シェルは終了コードを確認する必要があります。

INTところで、シェルでこれを行うのは非常に複雑な場合があります。シェルがシグナルを無視する必要がある場合もあれば、そうでない場合もあるからです)tail -f /etc/passwd; echo foo

答え4

まあ、私はsleep 1bashスクリプトにaを追加しようとしましたが、bam!
これで2つで止めることができますCtrl+C

を押すと、現在実行中のプロセスにCtrl+C信号SIGINTが送信され、コマンドはループ内で実行されます。次に、サブシェルプロセスは、他のプロセスを開始するループの次のコマンドに続く。スクリプトを停止するには、2つのSIGINTシグナルを送信する必要があります。 1 つは現在実行中のコマンドを中断し、もう 1 つは現在実行中のコマンドを中断します。サブシェルプロセス。

が呼び出されていないスクリプトでは、非常に迅速に複数回sleep押すことはCtrl+C機能しないようで、ループを終了することもできません。私の考えでは、2回押すことは、現在実行中のプロセスが中断され、次のプロセスが開始される間の正確な瞬間に置くのに十分速くはないようです。押すたびにループ内で実行中のプロセスにCtrl+Caが送信されますが、SIGINTサブシェル

withスクリプトでは、sleep 1この呼び出しは最初Ctrl+C(first SIGINT)によって中断されると1秒間実行を一時停止します。サブシェル次のコマンドを実行するにはさらに時間がかかります。 2番目の項目Ctrl+C(2番目の項目SIGINT)は次に移動します。サブシェル、スクリプトの実行が終了します。

関連情報