Domanda

Quando uso il comando trap in bash, il precedente trap per il segnale dato viene sostituito.

C'è un modo per accendere più di un trap per lo stesso segnale?

È stato utile?

Soluzione

Modificare:

Sembra che ho letto male la domanda.La risposta è semplice:

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

trap handler3 SIGNAL1 SIGNAL2 ...

Originale:

Basta elencare più segnali alla fine del comando:

trap function-name SIGNAL1 SIGNAL2 SIGNAL3 ...

Puoi trovare la funzione associata a un particolare segnale usando trap -p:

trap -p SIGINT

Nota che elenca ogni segnale separatamente anche se sono gestiti dalla stessa funzione.

Puoi aggiungere un segnale aggiuntivo dato uno noto in questo modo:

eval "$(trap -p SIGUSR1) SIGUSR2"

Funziona anche se ci sono altri segnali aggiuntivi elaborati dalla stessa funzione.In altre parole, supponiamo che una funzione stesse già gestendo tre segnali: potresti aggiungerne altri due semplicemente facendo riferimento a uno esistente e aggiungendone altri due (dove solo uno è mostrato sopra appena all'interno delle virgolette di chiusura).

Se stai usando Bash >= 3.2, puoi fare qualcosa del genere per estrarre la funzione data un segnale.Nota che non è completamente robusto perché potrebbero apparire altre virgolette singole.

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

Quindi potresti ricostruire il tuo comando trap da zero se avessi bisogno di usare il nome della funzione, ecc.

Altri suggerimenti

Tecnicamente non puoi impostare più trap per lo stesso segnale, ma puoi aggiungere a una trap esistente:

  1. Recupera il codice trap esistente usando trap -p
  2. Aggiungi il tuo comando, separato da un punto e virgola o da una nuova riga
  3. Imposta la trappola sul risultato di #2

Ecco una funzione bash che fa quanto sopra:

# 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

Esempio di utilizzo:

trap_add 'echo "in trap DEBUG"' DEBUG

No

Il meglio che potresti fare è eseguire più comandi da un singolo trap per un dato segnale, ma non puoi avere più trap simultanee per un singolo segnale.Per esempio:

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

La prima riga imposta una trappola sul segnale 2 (SIGINT).La seconda riga stampa le trappole correnti: dovresti catturare l'output standard da questo e analizzarlo per il segnale desiderato. Quindi, puoi aggiungere il tuo codice a ciò che era già presente, notando che il codice precedente molto probabilmente includerà un'operazione di "uscita".La terza invocazione di trap cancella la trappola su 2/INT.L'ultimo mostra che non ci sono trappole in sospeso.

Puoi anche usare trap -p INT o trap -p 2 per stampare la trap per un segnale specifico.

Mi è piaciuta la risposta di Richard Hansen, ma non mi interessano le funzioni incorporate, quindi un'alternativa è:

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

Non mi piaceva dover giocare con queste manipolazioni di stringhe che nel migliore dei casi creano confusione, quindi mi sono inventato qualcosa del genere:

(ovviamente puoi modificarlo per altri segnali)

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
}

Ecco un'altra opzione:

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

Utilizzo:

$ on_exit date
$ on_exit 'echo "Goodbye from '\''`uname`'\''!"'
$ exit
exit
Sat Jan 18 18:31:49 PST 2014
Goodbye from 'FreeBSD'!
tap# 

Non c'è modo di avere più gestori per la stessa trappola, ma lo stesso gestore può fare più cose.

L'unica cosa che non mi piace nelle varie altre risposte che fanno la stessa cosa è l'uso della manipolazione delle stringhe per ottenere la funzione trap corrente.Ci sono due semplici modi per farlo: array e argomenti.Argomenti è il più affidabile, ma prima mostrerò gli array.

Matrici

Quando si utilizzano gli array, ci si basa sul fatto che trap -p SIGNAL restituisce trap -- ??? SIGNAL, quindi qualunque sia il valore di ???, ci sono altre tre parole nell'array.

Pertanto puoi fare questo:

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

Quindi spieghiamo questo.Innanzitutto, la variabile trapDecl viene dichiarata come matrice.Se lo fai all'interno di una funzione, sarà anche locale, il che è conveniente.

Quindi assegniamo l'output di trap -p SIGNAL all'array.Per fare un esempio, supponiamo che tu lo stia eseguendo dopo aver eseguito il source osht (unit test per shell) e che il segnale sia EXIT. L'output di trap -p EXIT sarà trap -- '_osht_cleanup' EXIT, quindi l'assegnazione trapDecl verrà sostituita in questo modo:

trapDecl=(trap -- '_osht_cleanup' EXIT)

Le parentesi sono normali assegnazioni di array, quindi trapDecl diventa un array con quattro elementi: trap, --, '_osht_cleanup' e EXIT.

Quindi estraiamo il gestore corrente -- che potrebbe essere integrato nella riga successiva, ma per motivi di spiegazione l'ho assegnato prima a una variabile.Semplificando quella riga, sto facendo questo: currentHandler="${array[@]:offset:length}", che è la sintassi usata da Bash per dire di scegliere length elementi a partire dall'elemento offset. Poiché inizia a contare da 0, il numero 2 sarà '_osht_cleanup'. Successivamente, ${#trapDecl[@]} è il numero di elementiall'interno di trapDecl, che sarà 4 nell'esempio.Sottrai 3 perché ci sono tre elementi che non vuoi: trap, -- e EXIT. Non ho bisogno di usare $(...) attorno a quell'espressione perché l'espansione aritmetica è già eseguita sugli argomenti offset e length.

La riga finale esegue un eval, che viene utilizzato in modo che la shell interpreti le virgolette dall'output di trap. Se eseguiamo la sostituzione dei parametri su quella riga, nell'esempio si espande come segue:

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

Non lasciatevi confondere dalla doppia virgoletta al centro ('').Bash concatena semplicemente due stringhe di virgolette se sono una accanto all'altra.Ad esempio, '1'"2"'3''4' viene ampliato a 1234 da Bash.Oppure, per fare un esempio più interessante, 1" "2 è la stessa cosa di "1 2". Quindi eval prende quella stringa e la valuta, il che equivale a eseguire questo:

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

E questo gestirà correttamente la quotazione, trasformando tutto tra -- e EXIT in un unico parametro.

Per fare un esempio più complesso, sto anteponendo la pulizia di una directory al gestore osht, quindi il mio segnale EXIT ora ha questo:

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

Se lo assegni a trapDecl, avrà dimensione 6 a causa degli spazi sul gestore.Cioè, 'rm è un elemento, così come -fr, invece di 'rm -fr ...' è un singolo elemento.

Ma currentHandler otterrà tutti e tre gli elementi (6 - 3 = 3) e le virgolette funzioneranno quando eval verrà eseguito.

argomenti

Argomenti salta semplicemente tutta la parte di gestione dell'array e utilizza eval in anticipo per ottenere le virgolette corrette.Lo svantaggio è che sostituisci gli argomenti posizionali su bash, quindi è meglio farlo da una funzione.Questo è il codice, però:

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

La prima riga imposterà gli argomenti posizionali sull'output di trap -p SIGNAL. Utilizzando l'esempio della sezione Array, $1 sarà trap, $2 sarà --, $3 sarà _osht_cleanup (senza virgolette!) e $4 sarà EXIT.

La riga successiva è piuttosto semplice, ad eccezione di ${3:+;}. La sintassi ${X:+Y} significa "output Y se la variabile X non è impostata o è nulla" .Quindi si espande a ; se è impostato $3 o nient'altro (se non esisteva un gestore precedente per SIGNAL).

Mi è stato scritto un insieme di funzioni per me stesso per risolvere un po' questo compito in un modo conveniente.

Aggiornamento : l'implementazione qui è obsoleta e lasciata qui come dimostrazione.La nuova implementazione è più complessa, ha dipendenze, supporta una gamma più ampia di casi e abbastanza grande da collocare qui.

Nuova implementazione: https://sf.net/p/tacklelib/tacklelib/HEAD/tree/trunk/bash/tacklelib/traplib.sh

Ecco l'elenco delle caratteristiche della nuova implementazione:

Pro :

  1. Ripristina automaticamente il precedente gestore trap nelle funzioni nidificate. Originariamente la trap RETURN si ripristina SOLO se TUTTE le funzioni nello stack l'hanno impostata.
  2. La trappola del segnale RETURN può supportare altre trappole del segnale per ottenere il modello RAII come in altre lingue. Ad esempio, per disabilitare temporaneamente la gestione delle interruzioni e ripristinarla automaticamente al termine di una funzione mentre è in esecuzione un codice di inizializzazione.
  3. Protezione dalla chiamata non da un contesto di funzione in caso di trap del segnale RETURN.
  4. I gestori di segnale non RETURN nell'intero stack invocano insieme in un processo bash dal basso verso l'alto e li esegue nell'ordine invertito alle chiamate di funzione tkl_push_trap
  5. I gestori di trap di segnale RETURN invocano solo per una singola funzione dal basso verso l'alto in ordine inverso rispetto alle chiamate di funzione tkl_push_trap.
  6. Poiché il segnale EXIT non attiva il gestore della trap del segnale RETURN, il gestore della trap del segnale EXIT esegue automaticamente la configurazione almeno una volta per processo bash quando il gestore della trap del segnale RETURN esegue la configurazione per la prima volta in un processo bash. Ciò include tutti i processi bash, ad esempio, rappresentati come operatori (...) o $(...). Quindi i gestori di trap di segnale EXIT gestiscono automaticamente tutti i gestori di trap RETURN prima di eseguire se stessi.
  7. Il gestore di trap di segnale RETURN può ancora chiamare le funzioni tkl_push_trap e tkl_pop_trap per elaborare le trap di segnale non RETURN
  8. Il gestore di trap di segnale RETURN può chiamare la funzione tkl_set_trap_postponed_exit da entrambi i gestori di trap di segnale EXIT e RETURN. Se viene chiamato dal gestore di trap di segnale RETURN, il gestore di trap EXIT verrà chiamato dopo tutti i gestori di trap di segnale RETURN nel processo bash. Se viene chiamato dal gestore di trap di segnale EXIT, il gestore di trap EXIT cambierà il codice di uscita dopo che è stato richiamato l'ultimo gestore di trap di segnale EXIT.
  9. Accesso più rapido allo stack di trap come variabile globale invece di utilizzare gli operatori (...) o $(...) che invoca un processo bash esterno.
  10. Il comando source viene ignorato dal gestore di trap di segnale RETURN, quindi tutte le chiamate al comando source non invocheranno il codice utente di trap di segnale RETURN (contrassegnato in Pro, perché il gestore di trap di segnale RETURN deve essere chiamato solo dopo il ritorno dauna funzione in primo luogo e non da un'inclusione di script).

Contro :

  1. Non devi usare il comando trap integrato nel gestore passato alla funzione tkl_push_trap poiché le funzioni tkl_*_trap lo usano internamente.
  2. Non è necessario utilizzare il comando exit integrato nei gestori di segnale EXIT mentre il gestore di trap di segnale EXIT è in esecuzione.Altrimenti ciò lascerà il resto dei gestori di trap di segnale RETURN e EXIT non eseguiti. Per modificare il codice di uscita dal gestore EXIT puoi usare la funzione tkl_set_trap_postponed_exit per quello.
  3. Non è necessario utilizzare il comando return integrato nel gestore di trap di segnale RETURN mentre il gestore di trap di segnale RETURN è in esecuzione.Altrimenti ciò lascerà il resto dei gestori di trap di segnale RETURN e EXIT non eseguiti.
  4. Tutte le chiamate alle funzioni tkl_push_trap e tkl_pop_trap non hanno effetto se sono state chiamate da un gestore trap per un segnale che il gestore trap sta gestendo (chiamata ricorsiva attraverso il segnale).
  5. Devi sostituire tutti i comandi trap incorporati negli script nidificati o di terze parti con tkl_*_trap funzioni se stai già utilizzando la libreria.
  6. Il comando source viene ignorato dal gestore della trap del segnale RETURN, quindi tutte le chiamate al comando source non invocheranno il codice utente della trappola del segnale RETURN (contrassegnato nei Cons, a causa della perdita della retrocompatibilità qui).

Vecchia implementazione :

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
}

Utilizzo

cd ~/test
./test.sh

Produzione

~ ~/test
111
popd
~/test
--
~ ~/test
222
exitting...
popd
~/test

Aggiungo una versione leggermente più robusta dello script trap-add di Laurent Simone:

  • Consente di utilizzare comandi arbitrari come trap, inclusi quelli con ' caratteri
  • Funziona solo in bash;Potrebbe essere riscritto con sed invece della sostituzione del pattern bash, ma ciò lo renderebbe notevolmente più lento.
  • Soffre ancora di causare un'eredità indesiderata delle trappole nei subshell.

trap-add () {
    local handler=$(trap -p "$2")
    handler=${handler/trap -- \'/}    # /- Strip `trap '...' SIGNAL` -> ...
    handler=${handler%\'*}            # \-
    handler=${handler//\'\\\'\'/\'}   # <- Unquote quoted quotes ('\'')
    trap "${handler} $1;" "$2"
}

Semplici modi per farlo

  1. Se tutte le funzioni di gestione del segnale sono note contemporaneamente, allora è sufficiente quanto segue (ha detto dal Gionata):
trap 'handler1;handler2;handler3' EXIT
  1. Altrimenti, se esistono gestori esistenti che dovrebbero rimanere, è possibile aggiungere facilmente nuovi gestori in questo modo:
trap "$( trap -p EXIT | cut -f2 -d \' );newHandler" EXIT
  1. Se non sai se esistono gestori esistenti ma desideri mantenerli in questo caso, procedi come segue:
 handlers="$( trap -p EXIT | cut -f2 -d \' )"
 trap "${handlers}${handlers:+;}newHandler" EXIT
  1. Può essere fattorizzato in una funzione del genere:
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

Utilizzo:

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

Nota : questo funziona solo a patto che i gestori siano nomi di funzioni o semplici istruzioni che non contengano cote semplici (cotes semplici è in conflitto con cut -f2 -d \').

Vorrei proporre la mia soluzione di più funzioni trap per script semplici

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

Un caso speciale del Risposta di Richard Hansen (ottima idea).Di solito ne ho bisogno per le trappole EXIT.In tal caso:

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

O in questo modo, se vuoi:

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'"
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top