81

当我trap在 Bash 中使用命令时,trap给定信号的前一个将被替换。

有没有办法trap为同一个信号产生不止一次的火灾?

4

19 回答 19

68

从技术上讲,您不能为同一信号设置多个陷阱,但您可以添加到现有陷阱:

  1. 使用获取现有的陷阱代码trap -p
  2. 添加您的命令,以分号或换行符分隔
  3. 将陷阱设置为 #2 的结果

这是执行上述操作的 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
于 2011-09-02T18:39:41.350 回答
45

编辑:

看来我误读了这个问题。答案很简单:

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]}

然后,如果您需要使用函数名等,您可以从头开始重建您的陷阱命令。

于 2010-07-26T19:32:09.087 回答
11

您可以做的最好的事情是从一个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 INTtrap -p 2打印特定信号的陷阱。

于 2010-07-26T19:15:53.400 回答
6

我喜欢理查德汉森的回答,但我不关心嵌入式功能,所以替代方案是:

#===================================================================
# 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
}
于 2015-06-04T17:27:06.323 回答
5

这是另一种选择:

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# 
于 2014-01-19T02:36:06.447 回答
5

我不喜欢玩这些在最好的时候令人困惑的字符串操作,所以我想出了这样的东西:

(显然你可以为其他信号修改它)

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
}
于 2017-06-16T13:27:39.407 回答
3

我已经为自己编写了一组函数,以便以一种方便的方式解决这个任务。

更新:这里的实现已经过时,留在这里作为演示。新的实现更复杂,有依赖关系,支持更广泛的案例,而且相当大。

新实现:https ://sf.net/p/tacklelib/tacklelib/HEAD/tree/trunk/bash/tacklelib/traplib.sh

以下是新实现的功能列表:

优点

  1. 自动恢复嵌套函数中的先前陷阱处理程序。最初,RETURN仅当堆栈中的所有函数都设置了它时,陷阱才会恢复。
  2. RETURN信号陷阱可以支持其他信号陷阱来实现与其他语言一样的 RAII 模式。例如,在执行初始化代码时临时禁用中断处理并在函数结束时自动恢复它。
  3. RETURN在信号陷阱的情况下防止调用不是来自函数上下文。
  4. 整个堆栈中的非RETURN信号处理程序在一个bash进程中从底部到顶部一起调用,并按照与tkl_push_trap函数调用相反的顺序执行它们
  5. 信号陷阱处理程序仅从底部到顶部以与函数调用相反的RETURN顺序调用单个函数tkl_push_trap
  6. 因为EXIT信号不会触发RETURN信号陷阱处理程序,所以当信号陷阱处理程序第一次在 bash 进程中进行设置EXIT时,信号陷阱处理程序会在每个 bash 进程中至少自动设置一次。RETURN这包括所有 bash 进程,例如,表示为(...)$(...)运算符。因此,EXIT信号陷阱处理程序会在运行之前自动处理所有RETURN陷阱处理程序。
  7. RETURN信号陷阱处理程序仍然可以调用tkl_push_traptkl_pop_trap函数来处理非信号RETURN陷阱
  8. 信号陷阱处理程序可以从和信号陷阱 处理RETURN程序调用函数。如果从信号陷阱处理程序中调用,则陷阱处理程序将 在 bash 进程中的所有信号陷阱处理程序之后调用。如果从信号陷阱处理程序中调用,则陷阱处理程序将在调用最后一个信号陷阱处理程序后更改退出代码。tkl_set_trap_postponed_exitEXITRETURNRETURNEXITRETURNEXITEXITEXIT
  9. 更快地将陷阱堆栈作为全局变量访问,而不是使用调用外部 bash 进程的(...)or运算符。$(...)
  10. 该命令被信号陷阱处理程序source忽略,因此对命令的所有调用都不会调用信号陷阱用户代码(在 Pros 中标记,因为信号陷阱处理程序必须首先在从函数返回后调用,而不是从脚本包含)。RETURNsourceRETURNRETURN

缺点

  1. 您不能trap在传递给tkl_push_trap函数的处理程序中使用内置命令,因为tkl_*_trap函数确实在内部使用它。
  2. 当信号陷阱处理程序正在运行时,您不得在信号处理程序中使用内置exit命令。否则,这将使其余的和信号陷阱处理程序不被执行。要更改处理程序的退出代码,您可以使用函数。EXITEXITRETURNEXITEXITtkl_set_trap_postponed_exit
  3. 当信号陷阱处理程序正在运行时,您不能在信号陷阱处理程序中使用内置return命令。否则,这将使其余的和信号陷阱处理程序不被执行。RETURNRETURNRETURNEXIT
  4. 如果已从陷阱处理程序调用陷阱处理程序正在处理的信号(通过信号递归调用) ,则对tkl_push_trap和函数的所有调用都无效。tkl_pop_trap
  5. 如果已经使用该库,则trap必须用函数替换嵌套或 3dparty 脚本中的所有内置命令。tkl_*_trap
  6. 该命令被信号陷阱处理程序source忽略,因此对该命令的所有调用都不会调用信号陷阱用户代码(在 Cons 中标记,因为此处失去了向后兼容性)。RETURNsourceRETURN

旧实现

陷阱库

#!/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

测试.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
于 2018-05-07T00:31:17.057 回答
2

没有办法让同一个陷阱有多个处理程序,但是同一个处理程序可以做很多事情。

在做同样事情的各种其他答案中,我不喜欢的一件事是使用字符串操作来获取当前的陷阱函数。有两种简单的方法可以做到这一点:数组和参数。参数是最可靠的,但我将首先显示数组。

数组

When using arrays, you rely on the fact that trap -p SIGNAL returns trap -- ??? SIGNAL, so whatever is the value of ???, there are three more words in the array.

Therefore you can do this:

declare -a trapDecl
trapDecl=($(trap -p SIGNAL))
currentHandler="${trapDecl[@]:2:${#trapDecl[@]} - 3}"
eval "trap -- 'your handler;'${currentHandler} SIGNAL"

So let's explain this. First, variable trapDecl is declared as an array. If you do this inside a function, it will also be local, which is convenient.

Next we assign the output of trap -p SIGNAL to the array. To give an example, let's say you are running this after having sourced osht (unit testing for shell), and that the signal is EXIT. The output of trap -p EXIT will be trap -- '_osht_cleanup' EXIT, so the trapDecl assignment will be substituted like this:

trapDecl=(trap -- '_osht_cleanup' EXIT)

The parenthesis there are normal array assignment, so trapDecl becomes an array with four elements: trap, --, '_osht_cleanup' and EXIT.

Next we extract the current handler -- that could be inlined in the next line, but for explanation's sake I assigned it to a variable first. Simplifying that line, I'm doing this: currentHandler="${array[@]:offset:length}", which is the syntax used by Bash to say pick length elements starting at element offset. Since it starts counting from 0, number 2 will be '_osht_cleanup'. Next, ${#trapDecl[@]} is the number of elements inside trapDecl, which will be 4 in the example. You subtract 3 because there are three elements you don't want: trap, -- and EXIT. I don't need to use $(...) around that expression because arithmetic expansion is already performed on the offset and length arguments.

The final line performs an eval, which is used so that the shell will interpret the quoting from the output of trap. If we do parameter substitution on that line, it expands to the following in the example:

eval "trap -- 'your handler;''_osht_cleanup' EXIT"

Do not be confused by the double quote in the middle (''). Bash simply concatenates two quotes strings if they are next to each other. For example, '1'"2"'3''4' is expanded to 1234 by Bash. Or, to give a more interesting example, 1" "2 is the same thing as "1 2". So eval takes that string and evaluates it, which is equivalent to executing this:

trap -- 'your handler;''_osht_cleanup' EXIT

And that will handle the quoting correctly, turning everything between -- and EXIT into a single parameter.

To give a more complex example, I'm prepending a directory clean up to the osht handler, so my EXIT signal now has this:

trap -- 'rm -fr '\''/var/folders/7d/qthcbjz950775d6vn927lxwh0000gn/T/tmp.CmOubiwq'\'';_osht_cleanup' EXIT

If you assign that to trapDecl, it will have size 6 because of the spaces on the handler. That is, 'rm is one element, and so is -fr, instead of 'rm -fr ...' being a single element.

But currentHandler will get all three elements (6 - 3 = 3), and the quoting will work out when eval is run.

Arguments

Arguments just skips all the array handling part and uses eval up front to get the quoting right. The downside is that you replace the positional arguments on bash, so this is best done from a function. This is the code, though:

eval "set -- $(trap -p SIGNAL)"
trap -- "your handler${3:+;}${3}" SIGNAL

The first line will set the positional arguments to the output of trap -p SIGNAL. Using the example from the Arrays section, $1 will be trap, $2 will be --, $3 will be _osht_cleanup (no quotes!), and $4 will be EXIT.

The next line is pretty straightforward, except for ${3:+;}. The ${X:+Y} syntax means "output Y if the variable X is unset or null". So it expands to ; if $3 is set, or nothing otherwise (if there was no previous handler for SIGNAL).

于 2018-09-04T18:10:28.847 回答
2

Simple ways to do it

  1. If all the signal handling functions are known at the same time, then the following is sufficient (has said by Jonathan):

    trap 'handler1;handler2;handler3' EXIT
    
  2. Else, if there is an existing handler(s) that should stay, then new handlers can easily be added like this:

    trap "$( trap -p EXIT | cut -f2 -d \' );newHandler" EXIT
    
  3. If you don't know if there are existing handlers but want to keep them in this case, do the following:

 handlers="$( trap -p EXIT | cut -f2 -d \' )"
 trap "${handlers}${handlers:+;}newHandler" EXIT
  1. It can be factorized in a function like that:
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

Usage:

trap-add 'echo "Bye bye"' EXIT
trap-add 'echo "See you next time"' EXIT

Remark : This works only as long as the handlers are function names, or simple instructions that did not contain any simple code (simple code conflicts with cut -f2 -d \').

于 2019-11-04T08:14:03.883 回答
1

I add a slightly more robust version of Laurent Simon's trap-add script:

  • Allows using arbitrary commands as trap, including such with ' characters
  • Works only in bash; It could be rewritten with sed instead of bash pattern substitution, but that would make it significantly slower.
  • Still suffers from causing unwanted inheritance of the traps in subshells.

trap-add () {
    local handler=$(trap -p "$2")
    handler=${handler/trap -- \'/}    # /- Strip `trap '...' SIGNAL` -> ...
    handler=${handler%\'*}            # \-
    handler=${handler//\'\\\'\'/\'}   # <- Unquote quoted quotes ('\'')
    trap "${handler} $1;" "$2"
}
于 2020-02-18T22:54:05.310 回答
1

I would like to propose my solution of multiple trap functions for simple scripts

# 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...
}
于 2020-08-03T21:11:07.700 回答
1

Just like to add my simple version as an example.

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;

If this code is run, will print:

Version 2
Version 1
于 2021-05-24T04:34:02.227 回答
1

Here's how I usually do it. It's not much different from what other people have suggested here but my version seems dramatically simpler and so far it always worked as desired for me.

Somewhere in the code, set a trap:

trap "echo Hello" EXIT

and later on update it:

oldTrap=$(trap -p EXIT)
oldTrap=${oldTrap#"trap -- '"}
oldTrap=${oldTrap%"' EXIT"}; 
trap "$oldTrap; echo World" EXIT

finally, on exit

Hello
World
于 2021-11-26T17:40:50.460 回答
1

In this answer I implemented a simple solution. Here I implement another solution that is based on extracting of previous trap commands from trap -p output. But I don't know how much it is portable because I'm not sure that trap -p output is regulated. Maybe its format can be changed in future (but I doubt that).

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

But this solution doesn't work well with subshells. Unfortunately trap -p prints commands of outer shell. And we execute them in subshell after extracting.

trap_add 'echo AAA' EXIT
( trap_add 'echo BBB' EXIT; )

In the above example echo AAA is executed twice: first time in subshell and second time in outer shell.

We have to check whether we are in new subshell and if we are then we must not take commands from 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
}

Note that to avoid security issue you have to reset the trap_subshell variable at script startup.

trap_subshell=

Unfortunately the solution above works only with EXIT signal now. A generic solution that works with any signal is below.

# 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.
于 2021-12-03T17:24:52.773 回答
0

A special case of Richard Hansen's answer (great idea). I usually need it for EXIT traps. In such case:

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

Or this way, if you will:

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'"
于 2020-08-21T22:38:09.780 回答
0

Always assuming I remember to pass multiple code snippets in semi-colon delimited fashion (as per bash(1)s' requirement for multiple commands on a single line, it's rare that the following (or something similar to it) fails to fulfil my meagre requirements...

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
}
于 2020-12-14T13:46:38.947 回答
0

I'd like something simpler... :)

My humble contrib:

#!/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() keeps adding strings (commands) to a bash global array while execute_exit_traps() just loops thru that array and eval the commands

Executed script...

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
于 2021-03-24T13:14:25.637 回答
0

Another solution, which executes all functions starting with the name trap_:

trap 'eval $(declare -F | grep -oP "trap_[^ ]+" | tr "\n" ";")' EXIT

Now simply add as many trap functions as you like and they will be all executed:

# 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//\//_}"; }

Note: trap_remove_lock() in this example is not executed if condition exit 1 is met (which is what we want).

于 2021-10-27T09:14:07.617 回答
0

A simple solution is to save commands for trap to variable and, when adding new trap, to restore them from that variable.

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; }'

Unfortunately this solution does not work well with subshells, because subshell inherits outer shell variables and thus outer shell trap commands are executed in subshell.

trap_add 'echo AAA'
( trap_add 'echo BBB'; )

In the above example echo AAA is executed twice: first time in subshell and second time in outer shell.

We have to check whether we are in new subshell and if we are then we must not take commands from the trap_cmds variable.

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
}

Note that to avoid security issue you have to reset the used variables at script startup.

trap_subshell=
trap_cmds=

Otherwise someone who runs your script can inject their malicious commands via environment variables.

export trap_subshell=0
export trap_cmds='echo "I hacked you"'
./your_script

Generic version that works with arbitrary signals is below.

# 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 In this answer I implemented another solution that gets previous commands from trap -p output.

于 2021-12-03T16:03:56.427 回答