multiple bash traps for the same signal

2020-02-17 06:03发布

When I use the "trap" command in bash, the previous trap for the given signal is replaced.

Is there a way of making more than one trap fire for the same signal?

9条回答
乱世女痞
2楼-- · 2020-02-17 06:48

No

About the best you could do is run multiple commands from a single trap for a given signal, but you cannot have multiple concurrent traps for a single signal. For example:

$ trap "rm -f /tmp/xyz; exit 1" 2
$ trap
trap -- 'rm -f /tmp/xyz; exit 1' INT
$ trap 2
$ trap
$

The first line sets a trap on signal 2 (SIGINT). The second line prints the current traps — you would have to capture the standard output from this and parse it for the signal you want. Then, you can add your code to what was already there — noting that the prior code will most probably include an 'exit' operation. The third invocation of trap clears the trap on 2/INT. The last one shows that there are no traps outstanding.

You can also use trap -p INT or trap -p 2 to print the trap for a specific signal.

查看更多
霸刀☆藐视天下
3楼-- · 2020-02-17 06:51

I have been wrote a set of functions for myself to a bit resolve this task in a convenient way.

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
}

Usage

cd ~/test
./test.sh

Output

~ ~/test
111
popd
~/test
--
~ ~/test
222
exitting...
popd
~/test
查看更多
Emotional °昔
4楼-- · 2020-02-17 06:53

Edit:

It appears that I misread the question. The answer is simple:

handler1 () { do_something; }
handler2 () { do_something_else; }
handler3 () { handler1; handler2; }

trap handler3 SIGNAL1 SIGNAL2 ...

Original:

Just list multiple signals at the end of the command:

trap function-name SIGNAL1 SIGNAL2 SIGNAL3 ...

You can find the function associated with a particular signal using trap -p:

trap -p SIGINT

Note that it lists each signal separately even if they're handled by the same function.

You can add an additional signal given a known one by doing this:

eval "$(trap -p SIGUSR1) SIGUSR2"

This works even if there are other additional signals being processed by the same function. In other words, let's say a function was already handling three signals - you could add two more just by referring to one existing one and appending two more (where only one is shown above just inside the closing quotes).

If you're using Bash >= 3.2, you can do something like this to extract the function given a signal. Note that it's not completely robust because other single quotes could appear.

[[ $(trap -p SIGUSR1) =~ trap\ --\ \'([^\047]*)\'.* ]]
function_name=${BASH_REMATCH[1]}

Then you could rebuild your trap command from scratch if you needed to using the function name, etc.

查看更多
登录 后发表回答