Bash が rsync/subshell exec ステートメント中に割り込みをトラップしない
質問
コンテクスト:
サブシェルと EXIT 疑似信号のトラップを含む bash スクリプトがありますが、実行中に割り込みを適切にトラップしません。 rsync
. 。以下に例を示します。
#!/bin/bash
logfile=/path/to/file;
directory1=/path/to/dir
directory2=/path/to/dir
cleanup () {
echo "Cleaning up!"
#do stuff
trap - EXIT
}
trap '{
(cleanup;) | 2>&1 tee -a $logfile
}' EXIT
(
#main script logic, including the following lines:
(exec sleep 10;);
(exec rsync --progress -av --delete $directory1 /var/tmp/$directory2;);
) | 2>&1 tee -a $logfile
trap - EXIT #just in case cleanup isn't called for some reason
スクリプトのアイデアは次のとおりです。重要なロジックのほとんどは、パイプ経由で接続されるサブシェル内で実行されます。 tee
そしてログファイルに保存されるので、そうする必要はありません tee
メインロジックのすべての行をすべてログに記録します。サブシェルが終了するか、何らかの理由でスクリプトが停止されると (EXIT 疑似信号はこれらすべてのケースをキャプチャする必要があります)、トラップはそれをインターセプトし、 cleanup()
機能を有効にしてトラップを削除します。の rsync
そして sleep
コマンド (スリープは単なる例です) が実行されます exec
実行中に親スクリプトを強制終了した場合、ゾンビ プロセスの作成を防ぐため、実行時間が長い可能性のある各コマンドは独自のサブシェルでラップされるため、 exec
終了しても、スクリプト全体は終了しません。
問題:
スクリプトを中断した場合 ( kill
または CTRL+C) 実行/サブシェルのラップ中 sleep
コマンド、トラップは適切に機能し、「クリーンアップ!」が表示されます。エコーとログに記録されました。途中でスクリプトを中断すると、 rsync
コマンド、わかりました rsync
終わって、書く rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at rsync.c(544) [sender=3.0.6]
画面に表示されると、スクリプトは終了します。クリーンアップもトラップもありません。なぜ中断/強制終了しないのですか rsync
罠を発動させる?
使ってみました --no-detach
rsyncで切り替えましたが、何も変わりませんでした。私はbash 4.1.2、rsync 3.0.6、centOS 6.2を持っています。
解決
どこでも繰り返したり、すべてのサブシェルや実行プログラムをいじったりせずに、ポイント X からのすべての出力を tee にリダイレクトするだけにしてはどうでしょうか...(何か見逃してなかったらいいのですが)
#!/bin/bash
logfile=/path/to/file;
directory1=/path/to/dir
directory2=/path/to/dir
exec > >(exec tee -a $logfile) 2>&1
cleanup () {
echo "Cleaning up!"
#do stuff
trap - EXIT
}
trap cleanup EXIT
sleep 10
rsync --progress -av --delete $directory1 /var/tmp/$directory2
他のヒント
に加えて set -e
, 、欲しいと思います set -E
:
設定すると、ERR のトラップはシェル関数、コマンド置換、およびサブシェル環境で実行されるコマンドによって継承されます。このような場合、通常、ERR トラップは継承されません。
あるいは、コマンドをサブシェルでラップする代わりに、中括弧を使用すると、コマンド出力をリダイレクトできますが、コマンド出力は現在のシェルで実行されます。
トラップにINTを追加すると割り込みが適切にキャッチされます
trap '{
(cleanup;) | 2>&1 tee -a $logfile
}' EXIT INT
Bash は割り込みを正しくトラップしています。ただし、これでは、なぜスクリプトが終了時にトラップするのかという質問には答えられません。 sleep
中断されるか、なぜトリガーされないのか rsync
, ですが、スクリプトは想定どおりに動作します。お役に立てれば。
シェルはエラー時に終了するように設定されている可能性があります。
bash # enter subshell
set -e
trap "echo woah" EXIT
sleep 4
中断したら sleep
(^C) その後、サブシェルは次の理由で終了します。 set -e
そして印刷する woah
過程の中で。
また、少し関係ありませんが、あなたの trap - EXIT
(明示的に) サブシェル内にあるため、クリーンアップ関数が戻った後は効果がありません。
実験から明らかなことは、 rsync
などの他のツールと同様に動作します ping
また、呼び出し元の Bash 親からシグナルを継承しません。
したがって、これを少し創造的にして、次のようなことを行う必要があります。
$ cat rsync.bash
#!/bin/sh
set -m
trap '' SIGINT SIGTERM EXIT
rsync -avz LargeTestFile.500M root@host.mydom.com:/tmp/. &
wait
echo FIN
今それを実行すると:
$ ./rsync.bash
X11 forwarding request failed
building file list ... done
LargeTestFile.500M
^C^C^C^C^C^C^C^C^C^C
sent 509984 bytes received 42 bytes 92732.00 bytes/sec
total size is 524288000 speedup is 1027.96
FIN
ファイルが完全に転送されたことがわかります。
$ ll -h | grep Large
-rw-------. 1 501 games 500M Jul 9 21:44 LargeTestFile.500M
使い方
ここでのコツは、Bash に次のように伝えていることです。 set -m
その中のバックグラウンド ジョブに対するジョブ コントロールを無効にします。次に、バックグラウンドで rsync
そして、 wait
最後の実行コマンドを待機するコマンド、 rsync
, 、完成するまで。
次に、スクリプト全体を次のように保護します。 trap '' SIGINT SIGTERM EXIT
.