trap
Bash でコマンドを使用するtrap
と、指定されたシグナルの前のものが置き換えられます。
同じ信号に対して複数のtrap
発砲を行う方法はありますか?
技術的には、同じシグナルに複数のトラップを設定することはできませんが、既存のトラップに追加することはできます:
trap -p
上記を行うbash関数は次のとおりです。
# note: printf is used instead of echo to avoid backslash
# processing and to properly handle values that begin with a '-'.
log() { printf '%s\n' "$*"; }
error() { log "ERROR: $*" >&2; }
fatal() { error "$@"; exit 1; }
# appends a command to a trap
#
# - 1st arg: code to add
# - remaining args: names of traps to modify
#
trap_add() {
trap_add_cmd=$1; shift || fatal "${FUNCNAME} usage error"
for trap_add_name in "$@"; do
trap -- "$(
# helper fn to get existing trap command from output
# of trap -p
extract_trap_cmd() { printf '%s\n' "$3"; }
# print existing trap command with newline
eval "extract_trap_cmd $(trap -p "${trap_add_name}")"
# print the new trap command
printf '%s\n' "${trap_add_cmd}"
)" "${trap_add_name}" \
|| fatal "unable to add to trap ${trap_add_name}"
done
}
# set the trace attribute for the above function. this is
# required to modify DEBUG or RETURN traps because functions don't
# inherit them unless the trace attribute is set
declare -f -t trap_add
使用例:
trap_add 'echo "in trap DEBUG"' DEBUG
編集:
質問を読み間違えたようです。答えは簡単です。
handler1 () { do_something; }
handler2 () { do_something_else; }
handler3 () { handler1; handler2; }
trap handler3 SIGNAL1 SIGNAL2 ...
オリジナル:
コマンドの最後に複数のシグナルをリストするだけです。
trap function-name SIGNAL1 SIGNAL2 SIGNAL3 ...
以下を使用して、特定の信号に関連付けられた関数を見つけることができますtrap -p
。
trap -p SIGINT
同じ関数で処理されている場合でも、各シグナルが個別にリストされていることに注意してください。
これを行うことで、既知の信号を指定して追加の信号を追加できます。
eval "$(trap -p SIGUSR1) SIGUSR2"
これは、同じ関数によって処理されている他の追加の信号がある場合でも機能します。つまり、関数が既に 3 つのシグナルを処理しているとします。既存のシグナルを 1 つ参照し、さらに 2 つ追加するだけで、さらに 2 つを追加できます (上記の右引用符のすぐ内側には 1 つだけが示されています)。
Bash >= 3.2 を使用している場合は、次のようなことを実行して、シグナルが与えられた関数を抽出できます。他の一重引用符が表示される可能性があるため、完全に堅牢ではないことに注意してください。
[[ $(trap -p SIGUSR1) =~ trap\ --\ \'([^\047]*)\'.* ]]
function_name=${BASH_REMATCH[1]}
次に、関数名などを使用する必要がある場合は、trap コマンドを最初から再構築できます。
できる最善のtrap
方法は、特定のシグナルに対して 1 つのコマンドから複数のコマンドを実行することですが、1 つのシグナルに対して複数の同時トラップを実行することはできません。例えば:
$ trap "rm -f /tmp/xyz; exit 1" 2
$ trap
trap -- 'rm -f /tmp/xyz; exit 1' INT
$ trap 2
$ trap
$
最初の行は、シグナル 2 (SIGINT) にトラップを設定します。2 行目は、現在のトラップを出力します — これから標準出力をキャプチャし、必要なシグナルを解析する必要があります。次に、既存のコードにコードを追加できます — 以前のコードにはおそらく「終了」操作が含まれていることに注意してください。トラップの 3 回目の呼び出しは、2/INT のトラップをクリアします。最後の 1 つは、未解決のトラップがないことを示しています。
trap -p INT
またはtrap -p 2
を使用して、特定のシグナルのトラップを出力することもできます。
私はリチャード・ハンセンの答えが好きでしたが、組み込み関数は気にしないので、別の方法は次のとおりです。
#===================================================================
# FUNCTION trap_add ()
#
# Purpose: appends a command to a trap
#
# - 1st arg: code to add
# - remaining args: names of traps to modify
#
# Example: trap_add 'echo "in trap DEBUG"' DEBUG
#
# See: http://stackoverflow.com/questions/3338030/multiple-bash-traps-for-the-same-signal
#===================================================================
trap_add() {
trap_add_cmd=$1; shift || fatal "${FUNCNAME} usage error"
new_cmd=
for trap_add_name in "$@"; do
# Grab the currently defined trap commands for this trap
existing_cmd=`trap -p "${trap_add_name}" | awk -F"'" '{print $2}'`
# Define default command
[ -z "${existing_cmd}" ] && existing_cmd="echo exiting @ `date`"
# Generate the new command
new_cmd="${existing_cmd};${trap_add_cmd}"
# Assign the test
trap "${new_cmd}" "${trap_add_name}" || \
fatal "unable to add to trap ${trap_add_name}"
done
}
別のオプションは次のとおりです。
on_exit_acc () {
local next="$1"
eval "on_exit () {
local oldcmd='$(echo "$next" | sed -e s/\'/\'\\\\\'\'/g)'
local newcmd=\"\$oldcmd; \$1\"
trap -- \"\$newcmd\" 0
on_exit_acc \"\$newcmd\"
}"
}
on_exit_acc true
使用法:
$ on_exit date
$ on_exit 'echo "Goodbye from '\''`uname`'\''!"'
$ exit
exit
Sat Jan 18 18:31:49 PST 2014
Goodbye from 'FreeBSD'!
tap#
同じトラップに対して複数のハンドラーを持つ方法はありませんが、同じハンドラーで複数のことを行うことができます。
同じことをしている他のさまざまな回答で私が気に入らないことの1つは、現在のトラップ関数を取得するために文字列操作を使用することです。これを行うには、配列と引数の 2 つの簡単な方法があります。引数は最も信頼できるものですが、最初に配列を示します。
trap -p SIGNAL
配列を使用する場合、 が を返すという事実に依存するtrap -- ??? SIGNAL
ため、 の値が何であれ???
、配列にはさらに 3 つの単語があります。
したがって、これを行うことができます:
declare -a trapDecl
trapDecl=($(trap -p SIGNAL))
currentHandler="${trapDecl[@]:2:${#trapDecl[@]} - 3}"
eval "trap -- 'your handler;'${currentHandler} SIGNAL"
それでは、これを説明しましょう。まず、変数trapDecl
を配列として宣言します。これを関数内で行うとローカルにもなるので便利です。
trap -p SIGNAL
次に、出力を配列に代入します。例を挙げると、osht (シェルの単体テスト) をソースした後にこれを実行していて、シグナルがEXIT
. の出力はtrap -p EXIT
になるtrap -- '_osht_cleanup' EXIT
ため、trapDecl
割り当ては次のように置き換えられます。
trapDecl=(trap -- '_osht_cleanup' EXIT)
そこの括弧は通常の配列代入なので、 、 、trapDecl
の 4 つの要素を持つ配列になります。trap
--
'_osht_cleanup'
EXIT
次に、現在のハンドラーを抽出します。これは次の行でインライン化できますが、説明のために最初に変数に割り当てました。その行を単純化して、私はこれをやっています: currentHandler="${array[@]:offset:length}"
、これは Bash がlength
element から始まる要素を選択するために使用する構文offset
です。から数えるので0
、個数2
は になります'_osht_cleanup'
。次に${#trapDecl[@]}
は 内の要素の数でtrapDecl
、この例では 4 になります。trap
、--
およびの 3 つの不要な要素があるため、3 を引きますEXIT
。および引数$(...)
に対して算術展開が既に実行されているため、その式を使用する必要はありません。offset
length
最後の行は を実行しますeval
。これは、シェルが の出力からの引用を解釈するために使用されますtrap
。その行でパラメーター置換を行うと、例では次のように展開されます。
eval "trap -- 'your handler;''_osht_cleanup' EXIT"
真ん中の二重引用符 ( ) と混同しないでください''
。Bash は、2 つの引用符文字列が隣り合っている場合、それらを単純に連結します。たとえば、Bash によって'1'"2"'3''4'
展開されます。1234
または、より興味深い例を挙げると、1" "2
は と同じです"1 2"
。したがって、 eval はその文字列を取得して評価します。これは、これを実行することと同じです。
trap -- 'your handler;''_osht_cleanup' EXIT
これにより、引用符が正しく処理され、 ~ の間のすべてが1 つのパラメーターに変換されます--
。EXIT
より複雑な例を挙げると、ディレクトリのクリーンアップを osht ハンドラーの先頭に追加しているため、EXIT
シグナルは次のようになります。
trap -- 'rm -fr '\''/var/folders/7d/qthcbjz950775d6vn927lxwh0000gn/T/tmp.CmOubiwq'\'';_osht_cleanup' EXIT
それを に割り当てるとtrapDecl
、ハンドラーのスペースのためにサイズが 6 になります。つまり、'rm
は 1 つの要素であり、 も 1 つの要素ではありませ-fr
ん'rm -fr ...'
。
しかしcurrentHandler
、3 つの要素 (6 - 3 = 3) をすべて取得し、 をeval
実行すると引用が機能します。
引数は、すべての配列処理部分をスキップしeval
、前もって引用を正しく行うために使用します。欠点は、bash で位置引数を置き換えることです。そのため、これは関数から行うのが最適です。ただし、これはコードです。
eval "set -- $(trap -p SIGNAL)"
trap -- "your handler${3:+;}${3}" SIGNAL
最初の行は、位置引数を の出力に設定しますtrap -p SIGNAL
。配列セクションの例を使用すると、 will $1
be trap
、$2
will be --
、$3
will be _osht_cleanup
(引用符なし!)、および$4
will be になりますEXIT
。
次の行は、${3:+;}
. ${X:+Y}
構文は、「変数が設定されていないか nullの場合に出力する」Y
X
ことを意味します。したがって、が設定されて;
いる場合は展開されます。$3
SIGNAL
私の単純なバージョンを例として追加したいだけです。
trap -- 'echo "Version 1";' EXIT;
function add_to_trap {
local -a TRAP;
# this will put parts of trap command into TRAP array
eval "TRAP=($(trap -p EXIT))";
# 3rd field is trap command. Concat strings.
trap -- 'echo "Version 2"; '"${TRAP[2]}" EXIT;
}
add_to_trap;
このコードを実行すると、次のように出力されます。
Version 2
Version 1
簡単な解決策は、トラップのコマンドを変数に保存し、新しいトラップを追加するときに、その変数からコマンドを復元することです。
trap_add()
{
# Combine new and old commands. Separate them by newline.
trap_cmds="$1
$trap_cmds"
trap -- "$trap_cmds" EXIT
}
trap_add 'echo AAA'
trap_add '{ echo BBB; }'
残念ながら、このソリューションはサブシェルではうまく機能しません。サブシェルは外部シェル変数を継承するため、外部シェル トラップ コマンドがサブシェルで実行されるためです。
trap_add 'echo AAA'
( trap_add 'echo BBB'; )
上記の例echo AAA
では、2 回実行されます。1 回目はサブシェルで実行され、2 回目は外部シェルで実行されます。
新しいサブシェルにいるかどうかを確認する必要があります。新しいサブシェルにいる場合は、trap_cmds
変数からコマンドを取得してはなりません。
trap_add()
{
# Avoid inheriting trap commands from outer shell.
if [[ "${trap_subshell:-}" != "$BASH_SUBSHELL" ]]; then
# We are in new subshell, don't take commands from outer shell.
trap_subshell="$BASH_SUBSHELL"
trap_cmds=
fi
# Combine new and old commands. Separate them by newline.
trap_cmds="$1
$trap_cmds"
trap -- "$trap_cmds" EXIT
}
セキュリティ上の問題を回避するには、スクリプトの起動時に使用された変数をリセットする必要があることに注意してください。
trap_subshell=
trap_cmds=
そうしないと、スクリプトを実行する誰かが環境変数を介して悪意のあるコマンドを挿入する可能性があります。
export trap_subshell=0
export trap_cmds='echo "I hacked you"'
./your_script
任意の信号で動作する汎用バージョンを以下に示します。
# Check if argument is number.
is_num()
{
[ -n "$1" ] && [ "$1" -eq "$1" ] 2>/dev/null
}
# Convert signal name to signal number.
to_sig_num()
{
if is_num "$1"; then
# Signal is already number.
kill -l "$1" >/dev/null # Check that signal number is valid.
echo "$1" # Return result.
else
# Convert to signal number.
kill -l "$1"
fi
}
trap_add()
{
local cmd sig sig_num
cmd="$1"
sig="$2"
sig_num=$(to_sig_num "$sig")
# Avoid inheriting trap commands from outer shell.
if [[ "${trap_subshell[$sig_num]:-}" != "$BASH_SUBSHELL" ]]; then
# We are in new subshell, don't take commands from outer shell.
trap_subshell[$sig_num]="$BASH_SUBSHELL"
trap_cmds[$sig_num]=
fi
# Combine new and old commands. Separate them by newline.
trap_cmds[$sig_num]="$cmd
${trap_cmds[$sig_num]}"
trap -- "${trap_cmds[$sig_num]}" $sig
}
trap_subshell=
trap_cmds=
trap_add 'echo AAA' EXIT
trap_add '{ echo BBB; }' 0 # The same as EXIT.
PSこの回答trap -p
では、出力から以前のコマンドを取得する別のソリューションを実装しました。