Bashでtrap
コマンドを使用すると、指定した信号の以前のtrap
が置き換えられます。
同じシグナルに対して複数のtrap
を発生させる方法はありますか?
編集:
私はその質問を誤解しているようです。答えは簡単です:
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 -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
できる最善の方法は、特定の信号に対して単一のtrap
から複数のコマンドを実行することですが、単一の信号に対して複数の同時トラップを持つことはできません。例えば:
$ trap "rm -f /tmp/xyz; exit 1" 2
$ trap
trap -- 'rm -f /tmp/xyz; exit 1' INT
$ trap 2
$ trap
$
最初の行は、シグナル2(SIGINT)にトラップを設定します。 2行目は現在のトラップを出力します。これから標準出力をキャプチャして、必要な信号を解析する必要があります。次に、既存のコードにコードを追加できます。前のコードには「exit」操作が含まれている可能性が高いことに注意してください。トラップの3番目の呼び出しは、2/INTでトラップをクリアします。最後のものは、未解決のトラップがないことを示しています。
trap -p INT
またはtrap -p 2
を使用して、特定の信号のトラップを出力することもできます。
Richard Hansenの答えは気に入りましたが、組み込み関数は気にしません。
#===================================================================
# 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#
最高の時に混乱するこれらの文字列操作で遊ぶ必要がなかったので、次のようなものを思いつきました。
(明らかに、他の信号用に変更できます)
exit_trap_command=""
function cleanup {
eval "$exit_trap_command"
}
trap cleanup EXIT
function add_exit_trap {
local to_add=$1
if [[ -z "$exit_trap_command" ]]
then
exit_trap_command="$to_add"
else
exit_trap_command="$exit_trap_command; $to_add"
fi
}
このタスクを簡単に解決するための一連の関数を自分で作成しました。
traplib.sh
#!/bin/bash
# Script can be ONLY included by "source" command.
if [[ -n "$BASH" && (-z "$BASH_LINENO" || ${BASH_LINENO[0]} -gt 0) ]] && (( ! ${#SOURCE_TRAPLIB_SH} )); then
SOURCE_TRAPLIB_SH=1 # including guard
function GetTrapCmdLine()
{
local IFS=$' \t\r\n'
GetTrapCmdLineImpl RETURN_VALUES "$@"
}
function GetTrapCmdLineImpl()
{
local out_var="$1"
shift
# drop return values
eval "$out_var=()"
local IFS
local trap_sig
local stack_var
local stack_arr
local trap_cmdline
local trap_prev_cmdline
local i
i=0
IFS=$' \t\r\n'; for trap_sig in "$@"; do
stack_var="_traplib_stack_${trap_sig}_cmdline"
declare -a "stack_arr=(\"\${$stack_var[@]}\")"
if (( ${#stack_arr[@]} )); then
for trap_cmdline in "${stack_arr[@]}"; do
declare -a "trap_prev_cmdline=(\"\${$out_var[i]}\")"
if [[ -n "$trap_prev_cmdline" ]]; then
eval "$out_var[i]=\"\$trap_cmdline; \$trap_prev_cmdline\"" # the last srored is the first executed
else
eval "$out_var[i]=\"\$trap_cmdline\""
fi
done
else
# use the signal current trap command line
declare -a "trap_cmdline=(`trap -p "$trap_sig"`)"
eval "$out_var[i]=\"\${trap_cmdline[2]}\""
fi
(( i++ ))
done
}
function PushTrap()
{
# drop return values
EXIT_CODES=()
RETURN_VALUES=()
local cmdline="$1"
[[ -z "$cmdline" ]] && return 0 # nothing to Push
shift
local IFS
local trap_sig
local stack_var
local stack_arr
local trap_cmdline_size
local prev_cmdline
IFS=$' \t\r\n'; for trap_sig in "$@"; do
stack_var="_traplib_stack_${trap_sig}_cmdline"
declare -a "stack_arr=(\"\${$stack_var[@]}\")"
trap_cmdline_size=${#stack_arr[@]}
if (( trap_cmdline_size )); then
# append to the end is equal to Push trap onto stack
eval "$stack_var[trap_cmdline_size]=\"\$cmdline\""
else
# first stack element is always the trap current command line if not empty
declare -a "prev_cmdline=(`trap -p $trap_sig`)"
if (( ${#prev_cmdline[2]} )); then
eval "$stack_var=(\"\${prev_cmdline[2]}\" \"\$cmdline\")"
else
eval "$stack_var=(\"\$cmdline\")"
fi
fi
# update the signal trap command line
GetTrapCmdLine "$trap_sig"
trap "${RETURN_VALUES[0]}" "$trap_sig"
EXIT_CODES[i++]=$?
done
}
function PopTrap()
{
# drop return values
EXIT_CODES=()
RETURN_VALUES=()
local IFS
local trap_sig
local stack_var
local stack_arr
local trap_cmdline_size
local trap_cmd_line
local i
i=0
IFS=$' \t\r\n'; for trap_sig in "$@"; do
stack_var="_traplib_stack_${trap_sig}_cmdline"
declare -a "stack_arr=(\"\${$stack_var[@]}\")"
trap_cmdline_size=${#stack_arr[@]}
if (( trap_cmdline_size )); then
(( trap_cmdline_size-- ))
RETURN_VALUES[i]="${stack_arr[trap_cmdline_size]}"
# unset the end
unset $stack_var[trap_cmdline_size]
(( !trap_cmdline_size )) && unset $stack_var
# update the signal trap command line
if (( trap_cmdline_size )); then
GetTrapCmdLineImpl trap_cmd_line "$trap_sig"
trap "${trap_cmd_line[0]}" "$trap_sig"
else
trap "" "$trap_sig" # just clear the trap
fi
EXIT_CODES[i]=$?
else
# nothing to pop
RETURN_VALUES[i]=""
fi
(( i++ ))
done
}
function PopExecTrap()
{
# drop exit codes
EXIT_CODES=()
local IFS=$' \t\r\n'
PopTrap "$@"
local cmdline
local i
i=0
IFS=$' \t\r\n'; for cmdline in "${RETURN_VALUES[@]}"; do
# execute as function and store exit code
eval "function _traplib_immediate_handler() { $cmdline; }"
_traplib_immediate_handler
EXIT_CODES[i++]=$?
unset _traplib_immediate_handler
done
}
fi
test.sh
#/bin/bash
source ./traplib.sh
function Exit()
{
echo exitting...
exit $@
}
pushd ".." && {
PushTrap "echo popd; popd" EXIT
echo 111 || Exit
PopExecTrap EXIT
}
GetTrapCmdLine EXIT
echo -${RETURN_VALUES[@]}-
pushd ".." && {
PushTrap "echo popd; popd" EXIT
echo 222 && Exit
PopExecTrap EXIT
}
使用方法
cd ~/test
./test.sh
出力
~ ~/test
111
popd
~/test
--
~ ~/test
222
exitting...
popd
~/test
同じトラップに複数のハンドラーを設定する方法はありませんが、同じハンドラーで複数の処理を実行できます。
同じことをする他のさまざまな答えで私が気に入らないのは、現在のトラップ関数を取得するための文字列操作の使用です。これを行うには、配列と引数の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(Shellの単体テスト)をソースとして実行し、シグナルがEXIT
であるとします。 _trap -p EXIT
_の出力は_trap -- '_osht_cleanup' EXIT
_になるため、trapDecl
割り当ては次のように置き換えられます。
_trapDecl=(trap -- '_osht_cleanup' EXIT)
_
括弧には通常の配列割り当てがあるため、trapDecl
はtrap
、_--
_、_'_osht_cleanup'
_およびEXIT
の4つの要素を持つ配列になります。
次に、現在のハンドラーを抽出します。これは次の行でインライン化できますが、説明のために、まずハンドラーを変数に割り当てました。その行を簡略化して、私は次のようにしています:_currentHandler="${array[@]:offset:length}"
_、これはBashが要素length
で始まるoffset
要素を選択するために使用する構文です。 _0
_からカウントを開始するため、数値_2
_は_'_osht_cleanup'
_になります。次に、_${#trapDecl[@]}
_はtrapDecl
内の要素の数で、この例では4です。不要な3つの要素、trap
、_--
_およびEXIT
があるため、3を減算します。 offset
およびlength
引数で算術展開が既に実行されているため、その式の周囲で$(...)
を使用する必要はありません。
最後の行はeval
を実行します。これは、シェルがtrap
の出力からの引用を解釈するために使用されます。その行でパラメーター置換を行うと、例では次のように展開されます。
_eval "trap -- 'your handler;''_osht_cleanup' EXIT"
_
中央の二重引用符(_''
_)と混同しないでください。 Bashは、2つの引用符文字列が隣接している場合、それらを単に連結します。たとえば、_'1'"2"'3''4'
_は、Bashによって_1234
_に展開されます。または、より興味深い例を示すと、_1" "2
_は_"1 2"
_と同じです。したがって、evalはその文字列を取得して評価します。これは、次を実行するのと同じです。
_trap -- 'your handler;''_osht_cleanup' EXIT
_
そして、これは引用を正しく処理し、_--
_とEXIT
の間のすべてを単一のパラメーターに変換します。
より複雑な例を示すために、oshtハンドラーの前にディレクトリのクリーンアップを追加するため、EXIT
信号には次のようになります。
_trap -- 'rm -fr '\''/var/folders/7d/qthcbjz950775d6vn927lxwh0000gn/T/tmp.CmOubiwq'\'';_osht_cleanup' EXIT
_
これをtrapDecl
に割り当てると、ハンドラーのスペースのため、サイズは6になります。つまり、_'rm
_は1つの要素であり、_-fr
_は単一の要素ではなく、_'rm -fr ...'
_も1つの要素です。
しかし、currentHandler
は3つの要素(6-3 = 3)をすべて取得し、eval
が実行されると、クォートが機能します。
引数はすべての配列処理部分をスキップし、引用を正しくするためにeval
を前に使用します。欠点は、bashの位置引数を置き換えることです。そのため、これは関数から行うのが最適です。しかし、これはコードです:
_eval "set -- $(trap -p SIGNAL)"
trap -- "your handler${3:+;}${3}" SIGNAL
_
最初の行は、位置引数を_trap -p SIGNAL
_の出力に設定します。配列セクションの例を使用すると、_$1
_はtrap
、_$2
_は_--
_、_$3
_は__osht_cleanup
_になります(引用符なし) !)、および_$4
_はEXIT
になります。
次の行は、_${3:+;}
_を除いてかなり簡単です。 _${X:+Y}
_構文は"変数Y
が設定されていないかnullの場合、出力X
"を意味します。したがって、_;
_が設定されている場合は_$3
_に展開され、それ以外の場合は何も展開されません(SIGNAL
の以前のハンドラーがない場合)。
Laurent Simon のtrap-add
スクリプトのもう少し堅牢なバージョンを追加します。
'
文字を含む、任意のコマンドをトラップとして使用できるようにしますsed
を使用して書き換えることもできますが、これにより大幅に遅くなります。trap-add () {
local handler=$(trap -p "$2")
handler=${handler/trap -- \'/} # /- Strip `trap '...' SIGNAL` -> ...
handler=${handler%\'*} # \-
handler=${handler//\'\\\'\'/\'} # <- Unquote quoted quotes ('\'')
trap "${handler} $1;" "$2"
}
trap 'handler1;handler2;handler3' EXIT
trap "$( trap -p EXIT | cut -f2 -d \' );newHandler" EXIT
handlers="$( trap -p EXIT | cut -f2 -d \' )"
trap "${handlers}${handlers:+;}newHandler" EXIT
trap-add() {
local sig="${2:?Signal required}"
hdls="$( trap -p ${sig} | cut -f2 -d \' )";
trap "${hdls}${hdls:+;}${1:?Handler required}" "${sig}"
}
export -f trap-add
使用法:
trap-add 'echo "Bye bye"' EXIT
trap-add 'echo "See you next time"' EXIT
Remark:これは、ハンドラーが関数名であるか、単純なコートを含まない単純な命令(単純なコートはcut -f2 -d \'
)。