多个bash陷阱用于相同的信号

时间:2010-07-26 18:58:28

标签: bash bash-trap

当我在bash中使用“trap”命令时,替换给定信号的前一个陷阱。

是否有办法为同一个信号发射多个陷阱?

15 个答案:

答案 0 :(得分:42)

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

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

    这是一个执行上述操作的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 INTtrap -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 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"
}

答案 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的单元测试)之后运行了该程序,并且信号为EXITtrap -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。我不需要在该表达式周围使用$(...),因为已经对offsetlength参数进行了算术扩展。

最后一行执行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)

简单的方法

  1. 如果同时知道所有信号处理功能,则以下内容就足够了(Jonathan说过):
trap 'handler1;handler2;handler3' EXIT
  1. 否则,如果应该保留现有的处理程序,则可以像这样轻松添加新的处理程序:
trap "$( trap -p EXIT | cut -f2 -d \' );newHandler" EXIT
  1. 如果您不知道是否存在现有处理程序,但想在这种情况下保留它们,请执行以下操作:
 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 \'冲突)时,此方法才有效。

答案 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