日付を出力してリモートマシンに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?
何が起こるかは、bash
とping
の両方がSIGINTを受け取ることです(bash
はインタラクティブではありませんインタラクティブではありません。両方のping
とbash
は、作成され、端末のフォアグラウンドプロセスとして設定されている同じプロセスグループで実行されます。スクリプトを実行したインタラクティブシェルでグループ化します)。
ただし、bash
は、現在実行中のコマンドが終了した後でのみ、SIGINTを非同期で処理します。 bash
は、現在実行中のコマンドがSIGINTで終了した場合(つまり、終了ステータスがSIGINTによって強制終了されたことを示す場合)に、そのSIGINTを受け取ったときにのみ終了します。
$ bash -c 'sh -c "trap exit\ 0 INT; sleep 10; :"; echo here'
^Chere
上記では、Ctrl-Cを押すとbash
、sh
、sleep
はSIGINTを受け取りますが、sh
は通常0の終了コードで終了するため、bash
はSIGINTを無視するため、「ここ」に表示されます。
ping
、少なくともiputilsからのものは、そのように動作します。中断されると、統計が出力され、pingが応答されたかどうかに応じて0または1の終了ステータスで終了します。したがって、ping
の実行中にCtrl-Cを押すと、bash
はSIGINTハンドラーでCtrl-C
を押したことを記録しますが、ping
は正常に終了するため、bash
は終了しません。
sleep
にはSIGINTに特別なハンドラがないため、そのループにsleep 1
を追加してCtrl-C
を押すと、sleep
にSIGINTで特別なハンドラがないため、終了してbash
に報告されます。ケースbash
は終了します(割り込みを親に報告するために、実際にはSIGINTで自分自身を強制終了します)。
bash
がそのように動作する理由についてはわかりません。また、動作が常に確定的であるとは限りません。私はたった今質問しました bash
開発メーリングリストの質問 (Update:@Jillesが理由を書き留めました 彼の答え )。
同様に動作することがわかった他の唯一のシェルはksh93です(更新、 @ Jillesが述べたように、FreeBSDもsh
です)。そこでは、SIGINTは明らかに無視されているようです。また、ksh93
は、SIGINTによってコマンドが強制終了されると終了します。
上記のbash
と同じ動作が得られますが、次の点も異なります。
ksh -c 'sh -c "kill -INT \$\$"; echo test'
「テスト」を出力しません。つまり、SIGINTが終了するのを待っていたコマンドが、たとえSIGINTを受信しなかったとしても、それ自体がそのSIGINTを受信しなかった場合は、終了します(SIGINTで自分自身を殺すことにより)。
回避策は、以下を追加することです。
trap 'exit 130' INT
スクリプトの先頭で、SIGINTの受信時にbash
を強制的に終了します(どの場合でも、SIGINTは同期的に処理されません。現在実行中のコマンドが終了した後にのみ実行されます)。
理想的には、SIGINTが原因で死んだことを親に報告したいとします(たとえば、別のbash
スクリプトの場合、そのbash
スクリプトも中断されます)。 exit 130
を実行することは、SIGINTを終了することと同じではありません(ただし、シェルによっては$?
が両方のケースで同じ値に設定されます)。最も多い2)。
ただし、bash
、ksh93
、またはFreeBSD sh
の場合は機能しません。その130の終了ステータスはSIGINTによる終了とは見なされず、親スクリプトはそこで中止しません。
したがって、おそらくより良い代替案は、SIGINTを受け取ったときにSIGINTで自分を殺すことです。
trap '
trap - INT # restore default INT handler
kill -s INT "$$"
' INT
説明は、bashが http://www.cons.org/cracauer/sigint.html に従ってSIGINTおよびSIGQUITのWCE(待機および協調終了)を実装することです。つまり、bashがプロセスの終了を待機している間にSIGINTまたはSIGQUITを受信した場合、プロセスが終了するまで待機し、プロセスがそのシグナルで終了した場合、それ自体が終了します。これにより、ユーザーインターフェイスでSIGINTまたはSIGQUITを使用するプログラムが期待どおりに動作することが保証されます(信号によってプログラムが終了しなかった場合、スクリプトは正常に続行します)。
SIGINTまたはSIGQUITをキャッチするが、そのために終了するが、シグナルを自分自身に再送信する代わりに通常のexit()を使用するプログラムには、マイナス面があります。このようなプログラムを呼び出すスクリプトに割り込むことができない場合があります。 pingやping6などのプログラムには、実際の修正があると思います。
同様の動作はksh93とFreeBSDの/ bin/shで実装されていますが、他のほとんどのシェルでは実装されていません。
ご想像のとおり、これはSIGINTが下位プロセスに送信され、シェルがそのプロセスの終了後も継続していることが原因です。
これをより適切に処理するために、実行中のコマンドの終了ステータスを確認できます。 Unix戻りコードは、プロセスが終了した方法(システムコールまたはシグナル)と、exit()
に渡された値、またはプロセスを終了したシグナルの両方をエンコードします。これはすべてかなり複雑ですが、それを使用する最も速い方法は、シグナルによって終了されたプロセスがゼロ以外の戻りコードを持っていることを知ることです。したがって、スクリプトの戻りコードを確認すると、子プロセスが終了した場合に自分で終了することができ、不要なsleep
呼び出しなどの不必要な要素が不要になります。スクリプト全体でこれを行う簡単な方法は、set -e
、ただし、終了ステータスが期待される非ゼロであるコマンドには、いくつかの微調整が必要な場合があります。
端末は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
)
さて、sleep 1
をbashスクリプトに追加してください。
これで、2つで停止できます Ctrl+C。
押すと Ctrl+C、SIGINT
シグナルが現在実行されているプロセスに送信されます。このコマンドはループ内で実行されました。次に、サブシェルプロセスは、ループ内の次のコマンドの実行を継続し、別のプロセスを開始します。スクリプトを停止できるようにするには、2つのSIGINT
信号を送信する必要があります。1つは実行中の現在のコマンドを中断し、もう1つはサブシェルプロセスを中断します。
sleep
呼び出しのないスクリプトで、 Ctrl+C 本当に高速で何度も動作しないようで、ループを終了することはできません。私の推測では、2回押すのは、現在実行中のプロセスの中断と次のプロセスの開始との間の適切な瞬間にそれを行うには十分速くないと思います。毎 Ctrl+C を押すと、SIGINT
がループ内で実行されるプロセスに送信されますが、サブシェルには送信されません。
スクリプトでsleep 1
、この呼び出しは1秒間実行を一時停止し、最初の呼び出しによって中断されると Ctrl+C (最初のSIGINT
)、サブシェルは、次のコマンドを実行するのに時間がかかります。だから今、2番目 Ctrl+C (2番目のSIGINT
)はサブシェルに移動し、スクリプトの実行が終了します。
誰かがこのbash
機能の修正に興味があり、 その背後にある哲学 にそれほど関心がない場合は、以下が提案です。
問題のあるコマンドを直接実行しないでください。ただし、a)終了するのを待つラッパーからb)シグナルを乱さず、c)しますnotWCEメカニズム自体を実装しますが、SIGINT
を受信するとすぐに終了します。
このようなラッパーは、awk
+そのsystem()
関数で作成できます。
$ while true; do awk 'BEGIN{system("ping -c5 localhost")}'; done
PING localhost(localhost (::1)) 56 data bytes
64 bytes from localhost (::1): icmp_seq=1 ttl=64 time=0.082 ms
64 bytes from localhost (::1): icmp_seq=2 ttl=64 time=0.087 ms
^C
--- localhost ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1022ms
rtt min/avg/max/mdev = 0.082/0.084/0.087/0.009 ms
[3]- Terminated ping -c5 localhost
OPのようなスクリプトを入力します。
#!/bin/bash
while true; do
echo -e "\n*** DATE:" `date` " ***";
echo "********************************************"
awk 'BEGIN{system(ARGV[1])}' "ping -c5 ${1-localhost}"
done
pgrep -f process_name > any_file_name
sed -i 's/^/kill /' any_file_name
chmod 777 any_file_name
./any_file_name
たとえば、pgrep -f firefox
は、実行中のfirefox
のPIDをgrepし、このPIDをany_file_name
というファイルに保存します。 「sed」コマンドは、「any_file_name」ファイルのPID番号の先頭にkill
を追加します。 3行目はany_file_name
ファイルを実行可能にします。ここで4行目は、any_file_name
ファイルで使用可能なPIDを強制終了します。上記の4行をファイルに書き込み、そのファイルを実行すると、 Control-C。私にとってはまったく問題ありません。
これを試して:
#!/bin/bash
while true; do
echo "Ctrl-c works during sleep 5"
sleep 5
echo "But not during ping -c 5"
ping -c 5 127.0.0.1
done
次に、最初の行を次のように変更します。
#!/bin/sh
もう一度やり直してください-pingが中断可能かどうかを確認してください。