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

Simple ways to do it

  1. If all the signal handling functions are know at the same time, then the following is sufficient (has said by Jonathan):
trap 'handler1;handler2;handler3' EXIT
  1. Else, if there is existing handler(s) that should stay, then new handlers can easily be added like this:
trap "$( trap -p EXIT | cut -f2 -d \' );newHandler" EXIT
  1. If you don't know if there is 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 has long as the handlers are function names, or simple instructions that did not contains any simple cote (simple cotes conflicts with cut -f2 -d \').

查看更多
Root(大扎)
3楼-- · 2020-02-17 06:35

I didn't like having to play with these string manipulations which are confusing at the best of times, so I came up with something like this:

(obviously you can modify it for other signals)

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
}
查看更多
三岁会撩人
4楼-- · 2020-02-17 06:37

I liked Richard Hansen's answer, but I don't care for embedded functions so an alternate is:

#===================================================================
# 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
}
查看更多
Summer. ? 凉城
5楼-- · 2020-02-17 06:39

There's no way to have multiple handlers for the same trap, but the same handler can do multiple things.

The one thing I don't like in the various other answers doing the same thing is the use of string manipulation to get at the current trap function. There are two easy ways of doing this: arrays and arguments. Arguments is the most reliable one, but I'll show arrays first.

Arrays

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).

查看更多
小情绪 Triste *
6楼-- · 2020-02-17 06:45

Here's another option:

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

Usage:

$ on_exit date
$ on_exit 'echo "Goodbye from '\''`uname`'\''!"'
$ exit
exit
Sat Jan 18 18:31:49 PST 2014
Goodbye from 'FreeBSD'!
tap# 
查看更多
地球回转人心会变
7楼-- · 2020-02-17 06:48

Technically you can't set multiple traps for the same signal, but you can add to an existing trap:

  1. Fetch the existing trap code using trap -p
  2. Add your command, separated by a semicolon or newline
  3. Set the trap to the result of #2

Here is a bash function that does the above:

# 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

Example usage:

trap_add 'echo "in trap DEBUG"' DEBUG
查看更多
登录 后发表回答