当我在bash中使用“trap”命令时,替换给定信号的前一个陷阱。
是否有办法为同一个信号发射多个陷阱?
答案 0 :(得分:42)
从技术上讲,您无法为同一信号设置多个陷阱,但您可以添加到现有陷阱:
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
答案 1 :(得分:26)
修改强>
看来我误解了这个问题。答案很简单:
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"
即使其他信号由同一功能处理,也可以使用。换句话说,假设一个函数已经处理了三个信号 - 你可以通过引用一个现有函数再添加两个信号来增加两个信号(其中只有一个在结束引号内部显示)。
如果你正在使用Bash> = 3.2,你可以做这样的事情来提取给定信号的函数。请注意,它不是完全健壮的,因为可能会出现其他单引号。
[[ $(trap -p SIGUSR1) =~ trap\ --\ \'([^\047]*)\'.* ]]
function_name=${BASH_REMATCH[1]}
如果需要使用函数名等,则可以从头开始重建trap命令。
答案 2 :(得分:11)
关于您可以做的最好的事情是针对给定信号从单个trap
运行多个命令,但是对于单个信号,您不能有多个并发陷阱。例如:
$ trap "rm -f /tmp/xyz; exit 1" 2
$ trap
trap -- 'rm -f /tmp/xyz; exit 1' INT
$ trap 2
$ trap
$
第一行在信号2(SIGINT)上设置陷阱。第二行打印当前陷阱 - 您必须从此捕获标准输出并解析它以获得所需信号。 然后,您可以将代码添加到已存在的代码中 - 注意先前的代码很可能包含“退出”操作。第三次调用trap会清除2 / INT上的陷阱。最后一个显示没有未完成的陷阱。
您还可以使用trap -p INT
或trap -p 2
打印特定信号的陷阱。
答案 3 :(得分:6)
我喜欢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
}
答案 4 :(得分:3)
这是另一种选择:
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#
答案 5 :(得分:2)
我不喜欢玩这些在最好的时候令人困惑的字符串操作,所以我想出了类似的东西:
(显然你可以修改其他信号)
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
}
答案 6 :(得分:1)
我已经为自己写了一套函数,以方便的方式解决这个问题。
<强> 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
答案 7 :(得分:1)
我添加了Laurent Simon的trap-add
脚本的更健壮的版本:
'
个字符的命令sed
代替bash模式替换来重写它,但这会使它明显变慢。
trap-add () {
local handler=$(trap -p "$2")
handler=${handler/trap -- \'/} # /- Strip `trap '...' SIGNAL` -> ...
handler=${handler%\'*} # \-
handler=${handler//\'\\\'\'/\'} # <- Unquote quoted quotes ('\'')
trap "${handler} $1;" "$2"
}
答案 8 :(得分:0)
无法为同一个陷阱设置多个处理程序,但是同一个处理程序可以执行多项操作。
我在其他各种答案中不喜欢做一件事的一件事是使用字符串操作来获取当前的陷阱函数。有两种简单的方法:数组和参数。参数是最可靠的参数,但我将首先显示数组。
在使用数组时,您依赖于trap -p SIGNAL
返回trap -- ??? SIGNAL
的事实,因此无论???
的值是多少,数组中还有三个单词。
因此,您可以执行以下操作:
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
。>
接下来,我们提取当前处理程序-可以在下一行中内联,但是出于解释的原因,我首先将其分配给变量。为简化这一行,我正在这样做:currentHandler="${array[@]:offset:length}"
,这是Bash用来表示从元素length
开始选择offset
元素的语法。由于从0
开始计数,因此数字2
将为'_osht_cleanup'
。接下来,${#trapDecl[@]}
是trapDecl
中元素的数量,在示例中为4。减去3是因为不需要三个元素:trap
,--
和EXIT
。我不需要在该表达式周围使用$(...)
,因为已经对offset
和length
参数进行了算术扩展。
最后一行执行eval
,该行用来使外壳程序解释trap
输出中的引用。如果我们在那一行上进行参数替换,则在示例中它将扩展为以下内容:
eval "trap -- 'your handler;''_osht_cleanup' EXIT"
不要被中间的双引号(''
所迷惑。 Bash仅将两个引号字符串连接在一起(如果它们彼此相邻)。例如,'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
是一个元素,-fr
也是一个元素,而不是'rm -fr ...'
是单个元素。
但是currentHandler
将获得所有三个元素(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
未设置或为空,则输出X
” 。因此,如果设置了{{1},则扩展为;
,否则扩展为{(如果$3
之前没有处理程序)。
答案 9 :(得分:0)
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
备注:仅当处理程序为函数名或不包含任何简单字符的简单指令(简单字符与cut -f2 -d \'
冲突)时,此方法才有效。
答案 10 :(得分:0)
我想提出针对简单脚本的多个陷阱功能的解决方案
# Executes cleanup functions on exit
function on_exit {
for FUNCTION in $(declare -F); do
if [[ ${FUNCTION} == *"_on_exit" ]]; then
>&2 echo ${FUNCTION}
eval ${FUNCTION}
fi
done
}
trap on_exit EXIT
function remove_fifo_on_exit {
>&2 echo Removing FIFO...
}
function stop_daemon_on_exit {
>&2 echo Stopping daemon...
}
答案 11 :(得分:0)
Richard Hansen's answer的特例(好主意)。我通常需要EXIT陷阱。在这种情况下:
extract_trap_cmd() { printf '%s\n' "${3-}"; }
get_exit_trap_cmd() {
eval "extract_trap_cmd $(trap -p EXIT)"
}
...
trap "echo '1 2'; $(get_exit_trap_cmd)" EXIT
...
trap "echo '3 4'; $(get_exit_trap_cmd)" EXIT
或者,如果您愿意的话,
add_exit_trap_handler() {
trap "$1; $(get_exit_trap_cmd)" EXIT
}
...
add_exit_trap_handler "echo '5 6'"
...
add_exit_trap_handler "echo '7 8'"
答案 12 :(得分:0)
始终假设我记得以分号分隔的方式传递多个代码段(根据bash(1)s在一行上对多个命令的要求,很少有以下内容(或类似的内容)未能成功执行)满足我的微薄要求...
extend-trap() {
local sig=${1:?'Der, you forgot the sig!!!!'}
shift
local code s ; while IFS=\' read code s ; do
code="$code ; $*"
done < <(trap -p $sig)
trap "$code" $sig
}
答案 13 :(得分:0)
我不起眼的贡献:
#!/bin/bash
# global vars
TRAPS=()
# functions
function add_exit_trap() { TRAPS+=("$@") }
function execute_exit_traps() {
local I
local POSITIONS=${!TRAPS[@]} # retorna os índices válidos do array
for I in $POSITIONS
do
echo "executing TRAPS[$I]=${TRAPS[I]}"
eval ${TRAPS[I]}
done
}
# M A I N
LOCKFILE="/tmp/lock.me.1234567890"
touch $LOCKFILE
trap execute_exit_traps EXIT
add_exit_trap "rm -f $LOCKFILE && echo lock removed."
add_exit_trap "ls -l $LOCKFILE"
add_exit_trap "echo END"
echo "showing lockfile..."
ls -l $LOCKFILE
add_exit_trap()
不断向 bash 全局数组添加字符串(命令),同时
execute_exit_traps()
只是循环遍历该数组并评估命令
执行的脚本...
showing lockfile...
-rw-r--r--. 1 root root 0 Mar 24 10:08 /tmp/lock.me.1234567890
executing TRAPS[0]=rm -f /tmp/lock.me.1234567890 && echo lock removed.
lock removed.
executing TRAPS[1]=ls -l /tmp/lock.me.1234567890
ls: cannot access /tmp/lock.me.1234567890: No such file or directory
executing TRAPS[2]=echo END
END
答案 14 :(得分:0)
以我的简单版本为例。
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