私はリモートシステムに日付と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
です。ping
bash
いいえ対話型、両方ともスクリプトを実行する対話型シェルによって生成され、端末の前景プロセスグループとして設定された同じプロセスグループping
で実行されます。bash
ただし、bash
対応するSIGINTは、現在実行中のコマンドが終了した後にのみ非同期的に処理されます。bash
現在実行中のコマンドがSIGINTによって終了した場合(つまり、終了状況がSIGINTによって終了したことを示す)、SIGINTが受信されたときにのみ終了します。
$ bash -c 'sh -c "trap exit\ 0 INT; sleep 10; :"; echo here'
^Chere
上でbash
Ctrl-C を押すと SIGINT が受信されますが、sh
正常に 0 終了コードで終了するため、SIGINT は無視されるので「ここ」が表示されます。sleep
sh
bash
ping
、少なくともiputilsが動作する方法です。中断すると、統計情報が印刷され、ping 応答の有無によって終了ステータス 0 または 1 で終了します。したがって、ping
実行中にCtrl-Cを押すとSIGINTハンドラがbash
押されましたが、正常に終了するため終了しないことがわかります。Ctrl-C
ping
bash
sleep 1
そのループに1つを追加し、実行中Ctrl-C
に押すとSIGINTに特別なハンドラがないため、SIGINTで死んでSIGINTで死んだと報告します。この場合、終了します(実際にはSIGINTで自己終了します。順番に)、親エントリに割り込みを報告します。)sleep
sleep
bash
bash
この動作が発生する理由は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
スクリプトの上部でbash
SIGINT を受け取ると強制終了します。 SIGINTは、いかなる場合でも同期的に処理されず、現在実行中のコマンドが終了した後にのみ処理されます。
理想的にはSIGINTで死亡したことを親に報告したいと思います。したがって、他のbash
スクリプトの場合、そのbash
スクリプトも中断されます。 anを実行することはexit 130
SIGINTで死ぬのと同じではありませんが(一部のシェルは両方の場合に$?
同じ値を設定しますが)、SIGINTが死ぬことを報告するためによく使用されます(SIGINTが2のシステムではこれが最も多いです)。
しかし、FreeBSDbash
やksh93
FreeBSDでは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 1
bashスクリプトに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
)は次に移動します。サブシェル、スクリプトの実行が終了します。