【发布时间】:2011-03-21 06:19:21
【问题描述】:
当我在 Bash 中使用 trap 命令时,给定信号的先前 trap 将被替换。
有没有办法让多个trap 为同一个信号触发?
【问题讨论】:
-
我遇到了类似的问题并想出了this
当我在 Bash 中使用 trap 命令时,给定信号的先前 trap 将被替换。
有没有办法让多个trap 为同一个信号触发?
【问题讨论】:
您可以做的最好的事情是从单个trap 为给定信号运行多个命令,但您不能为单个信号设置多个并发陷阱。例如:
$ trap "rm -f /tmp/xyz; exit 1" 2
$ trap
trap -- 'rm -f /tmp/xyz; exit 1' INT
$ trap 2
$ trap
$
第一行在信号 2 (SIGINT) 上设置了一个陷阱。第二行打印当前的陷阱——您必须从中捕获标准输出并解析它以获得您想要的信号。 然后,您可以将您的代码添加到已经存在的代码中——注意前面的代码很可能包含一个“退出”操作。第三次调用陷阱清除 2/INT 上的陷阱。最后一个显示没有未完成的陷阱。
您还可以使用trap -p INT 或trap -p 2 打印特定信号的陷阱。
【讨论】:
编辑:
看来我误读了这个问题。答案很简单:
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 "xyz" 2'和'trap "pqr" 2',答案是(正如我所说)否。鉴于对问题的解释有所不同,答案的不同也就不足为奇了。
从技术上讲,您不能为同一信号设置多个陷阱,但您可以添加到现有陷阱:
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
【讨论】:
这是另一个选择:
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#
【讨论】:
我喜欢 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
}
【讨论】:
trap "echo 'foo'" EXIT),这将不起作用,这就是我的答案使用嵌入式函数的原因。
我不喜欢玩这些在最好的时候令人困惑的字符串操作,所以我想出了这样的东西:
(显然你可以为其他信号修改它)
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
}
【讨论】:
我已经为自己编写了一组函数来以一种方便的方式解决这个任务。
更新:这里的实现已经过时,留在这里作为演示。新的实现更复杂,有依赖,支持的案例范围更广,放在这里也很大。
新实现:https://sf.net/p/tacklelib/tacklelib/HEAD/tree/trunk/bash/tacklelib/traplib.sh
以下是新实现的功能列表:
优点:
RETURN 陷阱只有在堆栈中的所有函数都设置了它时才会恢复。RETURN 信号陷阱可以支持其他信号陷阱,以实现与其他语言一样的 RAII 模式。
例如,临时禁用中断处理并在函数结束时自动恢复它
在执行初始化代码时。RETURN 信号陷阱的情况下,防止调用不是来自函数上下文。RETURN 信号处理程序在 bash 进程中从
从底部到顶部并按照与tkl_push_trap 函数调用相反的顺序执行它们RETURN 信号陷阱处理程序仅调用从底部到顶部的单个函数
与tkl_push_trap 函数调用相反。EXIT 信号不会触发RETURN 信号陷阱处理程序,所以EXIT 信号陷阱
当RETURN 信号陷阱处理程序时,处理程序至少会在每个 bash 进程中自动设置一次
在 bash 进程中首次进行设置。
这包括所有 bash 进程,例如,表示为 (...) 或 $(...) 运算符。
因此EXIT 信号陷阱处理程序会在运行之前自动处理所有RETURN 陷阱处理程序。RETURN 信号陷阱处理程序仍然可以调用tkl_push_trap 和tkl_pop_trap 函数来处理
非RETURN 信号陷阱RETURN 信号陷阱处理程序可以从 EXIT 和
RETURN 信号陷阱处理程序。
如果从RETURN 信号陷阱处理程序调用,那么EXIT 陷阱处理程序将在所有
RETURN bash 进程中的信号陷阱处理程序。
如果从EXIT 信号陷阱处理程序中调用,则EXIT 陷阱处理程序将在之后更改退出代码
最后一个EXIT 信号陷阱处理程序被调用。(...) 或$(...) 运算符
它调用外部 bash 进程。source 命令会被RETURN 信号陷阱处理程序忽略,因此对source 命令的所有调用都不会
调用 RETURN 信号陷阱用户代码(在 Pros 中标记,因为 RETURN 信号陷阱处理程序必须是
仅在首先从函数返回后调用,而不是从脚本包含返回)。缺点:
tkl_push_trap 函数的处理程序中使用内置的trap 命令作为tkl_*_trap 函数
确实在内部使用它。EXIT 信号处理程序中使用内置的exit 命令,而EXIT 信号陷阱
处理程序正在运行。否则,其余的RETURN 和EXIT 信号陷阱处理程序将不被执行。
要更改 EXIT 处理程序的退出代码,您可以使用 tkl_set_trap_postponed_exit 函数。RETURN 信号陷阱处理程序中使用内置的return 命令,而RETURN 信号陷阱处理程序
在跑。否则,其余的 RETURN 和 EXIT 信号陷阱处理程序将不会被执行。tkl_push_trap 和tkl_pop_trap 函数的所有调用,则这些调用无效
陷阱处理程序正在处理的信号的处理程序(通过信号递归调用)。tkl_*_trap函数替换嵌套或3dparty脚本中的所有内置trap命令,如果
已在使用该库。source 命令会被RETURN 信号陷阱处理程序忽略,因此对source 命令的所有调用都不会
调用RETURN信号陷阱用户代码(在Cons中标记,因为这里失去了向后兼容性)。旧实现:
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
【讨论】:
没有办法让同一个陷阱有多个处理程序,但同一个处理程序可以做多件事。
在其他各种答案中我不喜欢做同样事情的一件事是使用字符串操作来获取当前的陷阱函数。有两种简单的方法可以做到这一点:数组和参数。参数是最可靠的,但我会先显示数组。
使用数组时,您依赖于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 用来表示从元素 offset 开始选择 length 元素的语法。由于它从0 开始计数,因此编号2 将是'_osht_cleanup'。接下来,${#trapDecl[@]} 是trapDecl 中的元素数,在示例中为 4。您减去 3 是因为您不想要三个元素:trap、-- 和 EXIT。我不需要在该表达式周围使用$(...),因为已经对offset 和length 参数执行了算术扩展。
最后一行执行eval,用于使shell 解释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 时引用将起作用。
Arguments 只是跳过所有数组处理部分,并预先使用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} 语法的意思是“如果变量X 未设置或为空,则输出Y”。因此,如果设置了$3,则它扩展为;,否则不设置(如果没有SIGNAL 的先前处理程序)。
【讨论】:
如果同时知道所有的信号处理函数,那么以下就足够了(Jonathan说过):
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 \' 冲突)。
【讨论】:
trap-add,子shell 将错误地继承处理程序,导致父shell 的退出陷阱被执行两次——当退出子shell 和退出父外壳时。例如。 onExit () { trap-add "echo -n .$1" exit; }; echo -n .start; onExit 'outer'; ( onExit 'inner' ); echo -n .end 产生输出 .start.outer.inner.end.outer。原因是,trap -p EXIT(不正确?)报告了父 shell 的陷阱,即使它没有被子 shell 继承。
我添加了一个稍微更健壮的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"
}
【讨论】:
我想为简单脚本提出多个陷阱函数的解决方案
# 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...
}
【讨论】:
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'"
【讨论】:
总是假设我记得以分号分隔的方式传递多个代码 sn-ps(根据 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
}
【讨论】:
我谦虚的贡献:
#!/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
【讨论】:
就像添加我的简单版本作为例子。
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_开头的所有函数:
trap 'eval $(declare -F | grep -oP "trap_[^ ]+" | tr "\n" ";")' EXIT
现在只需添加任意数量的陷阱函数,它们都会被执行:
# redirect output and errors to log file
exec 3>&1 4>&2
exec 1>> "/var/log/scripts/${0//\//_}.log" 2>&1
trap_reset_std() { exec 2>&4 1>&3; }
# make script race condition safe
[[ -d "/tmp/${0//\//_}" ]] || ! mkdir "/tmp/${0//\//_}" && echo "Script is already running!" && exit 1
trap_remove_lock() { rmdir "/tmp/${0//\//_}"; }
注意:如果满足条件exit 1(这是我们想要的),则不会执行此示例中的trap_remove_lock()。
【讨论】:
这是我通常的做法。它与其他人在这里建议的没有太大区别,但我的版本似乎非常简单,到目前为止它总是按我的意愿工作。
在代码的某处,设置一个陷阱:
trap "echo Hello" EXIT
稍后更新:
oldTrap=$(trap -p EXIT)
oldTrap=${oldTrap#"trap -- '"}
oldTrap=${oldTrap%"' EXIT"};
trap "$oldTrap; echo World" EXIT
最后,在退出时
Hello
World
【讨论】:
一个简单的解决方案是将陷阱命令保存到变量中,并在添加新陷阱时从该变量中恢复它们。
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; }'
不幸的是,这个解决方案不适用于子shell,因为子shell继承了外壳变量,因此外壳陷阱命令在子shell中执行。
trap_add 'echo AAA'
( trap_add 'echo BBB'; )
在上面的例子中echo AAA被执行了两次:第一次在子shell中,第二次在外壳中。
我们必须检查我们是否在新的子shell中,如果是,那么我们不能从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 在this answer 中,我实现了另一个解决方案,它从trap -p 输出中获取先前的命令。
【讨论】:
在this answer 我实现了一个简单的解决方案。在这里,我实现了另一个解决方案,它基于从trap -p 输出中提取先前的陷阱命令。但我不知道它的便携性有多少,因为我不确定trap -p 的输出是否受到监管。也许它的格式将来可以改变(但我对此表示怀疑)。
trap_add()
{
local new="$1"
local sig="$2"
# Get raw trap output.
local old="$(trap -p "$sig")"
# Extract previous commands from raw trap output.
old="${old#*\'}" # Remove first ' and everything before it.
old="${old%\'*}" # Remove last ' and everything after it.
old="${old//\'\\\'\'/\'}" # Replace every '\'' (escaped ') to just '.
# Combine new and old commands. Separate them by newline.
trap -- "$new
$old" "$sig"
}
trap_add 'echo AAA' EXIT
trap_add '{ echo BBB; }' EXIT
但是这个解决方案不适用于子shell。不幸的是trap -p 打印外壳的命令。解压后在子shell中执行。
trap_add 'echo AAA' EXIT
( trap_add 'echo BBB' EXIT; )
在上面的例子中echo AAA被执行了两次:第一次在子shell中,第二次在外壳中。
我们必须检查我们是否在新的子shell中,如果是,那么我们不能接受来自trap -p的命令。
trap_add()
{
local new="$1"
# 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"
local old=
else
# Get raw trap output.
local old="$(trap -p EXIT)"
# Extract previous commands from trap output.
old="${old#*\'}" # Remove first ' and everything before it.
old="${old%\'*}" # Remove last ' and everything after it.
old="${old//\'\\\'\'/\'}" # Replace every '\'' (escaped ') to just '.
fi
# Combine new and old commands. Separate them by newline.
trap -- "$new
$old" EXIT
}
请注意,为避免安全问题,您必须在脚本启动时重置 trap_subshell 变量。
trap_subshell=
不幸的是,上述解决方案现在仅适用于 EXIT 信号。下面是适用于任何信号的通用解决方案。
# 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 new="$1"
local sig="$2"
local sig_num
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"
local old=
else
# Get raw trap output.
local old="$(trap -p "$sig")"
# Extract previous commands from trap output.
old="${old#*\'}" # Remove first ' and everything before it.
old="${old%\'*}" # Remove last ' and everything after it.
old="${old//\'\\\'\'/\'}" # Replace every '\'' (escaped ') to just '.
fi
# Combine new and old commands. Separate them by newline.
trap -- "$new
$old" "$sig"
}
trap_subshell=
trap_add 'echo AAA' EXIT
trap_add '{ echo BBB; }' 0 # The same as EXIT.
【讨论】: