【问题标题】:Multiple Bash traps for the same signal同一信号的多个 Bash 陷阱
【发布时间】:2011-03-21 06:19:21
【问题描述】:

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

有没有办法让多个trap 为同一个信号触发?

【问题讨论】:

  • 我遇到了类似的问题并想出了this

标签: bash bash-trap


【解决方案1】:

没有

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

【讨论】:

    【解决方案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]}
    

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

    【讨论】:

    • 他要求对同一个信号使用多个陷阱,而不是对多个信号使用同一个陷阱。
    • @Darron:对不起,我误读了这个问题。 实际问题的答案要简单得多,我已将其添加到顶部。
    • 你仍然只是触发了一个陷阱——这个陷阱调用了多个函数并达到了效果,这或多或少是无可争辩的。认为可能存在任何争议的唯一理由是,如果首先安装了 handler1,并且它被设计为退出,那么 handler2 将不会被解雇。但是仍然只有一个动作被触发。 (您总是能够在触发的动作中进行任意复杂的操作序列。)
    • @Jonathan: handler3 可以在子shell 中运行 handler1 和 2 以便它可以确保两个处理程序都运行并执行其自己的退出(或者在子shell 中运行除最后一个之外的所有处理程序并让最后一个执行它)。此外,handler3 可以在 EXIT 上设置一个陷阱,并确保两个子处理程序都已运行。
    • 好的 - 我们得出的结论是,这个问题要求两个不同的东西。您假设问题是“您可以在陷阱处理程序中执行任意复杂的操作吗”,答案当然是肯定的 - 您可以在陷阱处理程序中编写任何 shell 脚本命令。我假设问题是“我可以同时激活两个单独的陷阱:'trap "xyz" 2'和'trap "pqr" 2',答案是(正如我所说)否。鉴于对问题的解释有所不同,答案的不同也就不足为奇了。
    【解决方案3】:

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

    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
    

    【讨论】:

    • 工作得很好(只要陷阱最初不是空的)
    【解决方案4】:

    这是另一个选择:

    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】:

      我喜欢 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),这将不起作用,这就是我的答案使用嵌入式函数的原因。
      【解决方案6】:

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

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

      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
      }
      

      【讨论】:

        【解决方案7】:

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

        更新:这里的实现已经过时,留在这里作为演示。新的实现更复杂,有依赖,支持的案例范围更广,放在这里也很大。

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

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

        优点

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

        缺点

        1. 您不能在传递给tkl_push_trap 函数的处理程序中使用内置的trap 命令作为tkl_*_trap 函数 确实在内部使用它。
        2. 您不能在EXIT 信号处理程序中使用内置的exit 命令,而EXIT 信号陷阱 处理程序正在运行。否则,其余的RETURNEXIT 信号陷阱处理程序将不被执行。 要更改 EXIT 处理程序的退出代码,您可以使用 tkl_set_trap_postponed_exit 函数。
        3. 您不能在RETURN 信号陷阱处理程序中使用内置的return 命令,而RETURN 信号陷阱处理程序 在跑。否则,其余的 RETURNEXIT 信号陷阱处理程序将不会被执行。
        4. 如果从陷阱中调用了对tkl_push_traptkl_pop_trap 函数的所有调用,则这些调用无效 陷阱处理程序正在处理的信号的处理程序(通过信号递归调用)。
        5. 你必须用tkl_*_trap函数替换嵌套或3dparty脚本中的所有内置trap命令,如果 已在使用该库。
        6. 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
        

        【讨论】:

          【解决方案8】:

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

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

          数组

          使用数组时,您依赖于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 的单元测试)之后运行它,并且信号是 EXITtrap -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。我不需要在该表达式周围使用$(...),因为已经对offsetlength 参数执行了算术扩展。

          最后一行执行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 的先前处理程序)。

          【讨论】:

            【解决方案9】:

            简单的方法

            1. 如果同时知道所有的信号处理函数,那么以下就足够了(Jonathan说过):

              trap 'handler1;handler2;handler3' EXIT
              
            2. 否则,如果应该保留现有的处理程序,则可以像这样轻松添加新的处理程序:

              trap "$( trap -p EXIT | cut -f2 -d \' );newHandler" EXIT
              
            3. 如果您不知道是否存在现有处理程序,但希望在这种情况下保留它们,请执行以下操作:

             handlers="$( trap -p EXIT | cut -f2 -d \' )"
             trap "${handlers}${handlers:+;}newHandler" EXIT
            
            1. 它可以分解成这样的函数:
            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 \' 冲突)。

            【讨论】:

            • 附加限制:如果在子shell 中使用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 继承。
            【解决方案10】:

            我添加了一个稍微更健壮的Laurent Simontrap-add 脚本版本:

            • 允许使用任意命令作为陷阱,包括带有' 字符的命令
            • 仅适用于 bash;可以使用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"
            }
            

            【讨论】:

              【解决方案11】:

              我想为简单脚本提出多个陷阱函数的解决方案

              # 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...
              }
              

              【讨论】:

                【解决方案12】:

                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'"
                

                【讨论】:

                  【解决方案13】:

                  总是假设我记得以分号分隔的方式传递多个代码 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
                  }
                  

                  【讨论】:

                    【解决方案14】:

                    我想要更简单的... :)

                    我谦虚的贡献:

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

                    【讨论】:

                      【解决方案15】:

                      就像添加我的简单版本作为例子。

                      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
                      

                      【讨论】:

                        【解决方案16】:

                        另一种解决方案,它执行以名称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()

                        【讨论】:

                          【解决方案17】:

                          这是我通常的做法。它与其他人在这里建议的没有太大区别,但我的版本似乎非常简单,到目前为止它总是按我的意愿工作。

                          在代码的某处,设置一个陷阱:

                          trap "echo Hello" EXIT
                          

                          稍后更新:

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

                          最后,在退出时

                          Hello
                          World
                          

                          【讨论】:

                            【解决方案18】:

                            一个简单的解决方案是将陷阱命令保存到变量中,并在添加新陷阱时从该变量中恢复它们。

                            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.
                            

                            PSthis answer 中,我实现了另一个解决方案,它从trap -p 输出中获取先前的命令。

                            【讨论】:

                              【解决方案19】:

                              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.
                              

                              【讨论】:

                                猜你喜欢
                                • 2016-12-18
                                • 2019-05-20
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 2012-04-06
                                相关资源
                                最近更新 更多