web-dev-qa-db-ja.com

「罠... INT TERM EXIT」は本当に必要?

trapの多くの例では、クリーンアップタスクにtrap ... INT TERM EXITを使用しています。しかし、3つのsigspecをすべてリストする必要があるのでしょうか。

マニュアルは言う:

SIGNAL_SPECがEXIT(0)の場合、シェルの終了時にARGが実行されます。

これは、スクリプトが正常に終了したか、SIGINTまたはSIGTERMを受け取ったために終了したかに関係なく当てはまります。実験も私の信念を裏付けています:

$ cat ./trap-exit
#!/bin/bash
trap 'echo TRAP' EXIT
sleep 3
$ ./trap-exit & sleep 1; kill -INT %1
[1] 759
TRAP
[1]+  Interrupt               ./trap-exit
$ ./trap-exit & sleep 1; kill -TERM %1
[1] 773
TRAP
[1]+  Terminated              ./trap-exit

それでは、なぜそんなに多くの例がINT TERM EXITのすべてをリストするのですか?それとも私は何かを見逃しましたか、唯一のEXITが見逃すケースはありますか?

71
musiphil

POSIX仕様 は、EXITトラップの実行につながる条件についてはあまり説明しません。実行時に環境がどのように見える必要があるかについてのみ説明します。

Busyboxのashシェルでは、SIGINTまたはSIGTERMが原因で、終了する前にトラップ終了テストが「TRAP」をエコーし​​ません。他のシェルも同様に機能しない可能性があると思われます。

# /tmp/test.sh & sleep 1; kill -INT %1
# 
[1]+  Interrupt                  /tmp/test.sh
# 
# 
# /tmp/test.sh & sleep 1; kill -TERM %1
# 
[1]+  Terminated                 /tmp/test.sh
# 
22
Shawn J. Goff

はい、違いがあります。

このスクリプトは、を押すと終了します Enter、または送信SIGINTまたはSIGTERM

trap '' EXIT
echo ' --- press ENTER to close --- '
read response

このスクリプトは、を押すと終了します Enter

trap '' EXIT INT TERM
echo ' --- press ENTER to close --- '
read response

*shBash、およびZshでテストされています。 (トラップを実行するコマンドを追加すると、shでは動作しなくなります)


AshDashEXITでシグナルをトラップしません。

したがって、信号をロバストに処理するには、EXITをまったくトラップしないようにし、次のようなものを使用するのが最善です。

cleanup() {
    echo "Cleaning stuff up..."
    exit
}

trap cleanup INT TERM
echo ' --- press ENTER to close --- '
read var
cleanup
28
Zaz

それが問題を持っているので、最後の答えを洗練する:

# Our general exit handler
cleanup() {
    err=$?
    echo "Cleaning stuff up..."
    trap '' EXIT INT TERM
    exit $err 
}
sig_cleanup() {
    trap '' EXIT # some shells will call EXIT after the INT handler
    false # sets $?
    cleanup
}
trap cleanup EXIT
trap sig_cleanup INT QUIT TERM

上記のポイント:

INTとTERMハンドラーは、テストしても終了しません。エラーを処理すると、シェルは終了に戻ります(これも驚くべきことではありません)。したがって、クリーンアップが後で終了することを確認します。信号の場合は常にエラーコードを使用します(通常の終了の場合は、エラーコードを保持します)。

Bashでは、INTハンドラーで終了するとEXITハンドラーも呼び出されるようです。そのため、出口ハンドラーのトラップを解除して自分で呼び出します(これは、動作に関係なくすべてのシェルで機能します)。

シェルスクリプトが最後に到達する前に終了する可能性があるため、私は終了をトラップします。一番下に行くシェルスクリプトに依存することはできません。

SIGQUITは、一度も試したことがない場合はCtrl- \です。ボーナスコアダンプを取得します。ですから、少しあいまいであっても、トラッピングする価値があると思います。

過去の経験では、(私と同じように)Ctrl-Cを常に数回押すと、シェルスクリプトのクリーンアップ部分の途中でそれが見つかる場合があるため、これは機能しますが、必ずしも完全に機能するとは限りません。

13
ijw