signal più trappole bash per lo stesso segnale




trap sh (7)

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

Notare che elenca ciascun segnale separatamente anche se sono gestiti dalla stessa funzione.

È possibile aggiungere un segnale aggiuntivo dato uno conosciuto facendo questo:

eval "$(trap -p SIGUSR1) SIGUSR2"

Funziona anche se altri segnali aggiuntivi vengono elaborati con la stessa funzione. In altre parole, diciamo che una funzione stava già gestendo tre segnali: si potevano aggiungere altri due semplicemente facendo riferimento a uno esistente e aggiungendone altri due (in cui solo uno è mostrato sopra solo all'interno delle virgolette di chiusura).

Se utilizzi Bash> = 3.2, puoi eseguire un'operazione come questa per estrarre la funzione in base a un segnale. Si noti che non è completamente robusto perché potrebbero apparire altre virgolette singole.

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

Quindi è possibile ricostruire il comando trap da zero se è necessario utilizzare il nome della funzione, ecc.

Quando uso il comando "trap" in bash, viene sostituita la trap precedente per il segnale specificato.

C'è un modo per fare più di un fuoco di trappola per lo stesso segnale?


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

  1. Scarica il codice trap esistente usando trap -p
  2. Aggiungi il tuo comando, separato da un punto e virgola o 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 "[email protected]"; 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 "[email protected]"; 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

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

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

Array

Quando si usano gli array, ci si basa sul fatto che trap -p SIGNAL restituisce trap -- ??? SIGNAL trap -- ??? SIGNAL , quindi qual è il valore di ??? ci sono altre tre parole nella matrice.

Pertanto puoi farlo:

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

Quindi spieghiamo questo. Innanzitutto, 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 stia eseguendo questo dopo aver acquisito 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) 

Tra parentesi ci sono normali assegnazioni di array, quindi trapDecl diventa una matrice con quattro elementi: trap , -- , '_osht_cleanup' ed EXIT .

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

La linea finale esegue una eval , che viene utilizzata in modo che la shell interpreti la citazione dall'output di trap . Se eseguiamo la sostituzione dei parametri su quella linea, nell'esempio seguente si espande nel modo seguente:

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

Non essere confuso dalla doppia citazione nel mezzo ( '' ). Bash semplicemente concatena due stringhe di virgolette se sono l'una accanto all'altra. Ad esempio, '1'"2"'3''4' viene espanso a 1234 da Bash. Oppure, per dare un esempio più interessante, 1" "2 è la stessa cosa di "1 2" . Quindi eval prende quella stringa e la valuta, che equivale all'esecuzione di questo:

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

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

Per dare un esempio più complesso, sto preparando una directory a pulire il gestore di osht, quindi il mio segnale di uscita ora ha questo:

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

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

Ma currentHandler otterrà tutti e tre gli elementi (6 - 3 = 3), e il quoting funzionerà quando eval verrà eseguito.

argomenti

Arguments salta semplicemente tutta la parte di manipolazione dell'array e usa eval in primo piano per ottenere il quoting right. Lo svantaggio è che si sostituiscono 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 . Usando l'esempio dalla sezione Array, $1 sarà trap , $2 sarà -- , $3 sarà _osht_cleanup (senza virgolette!), E $4 sarà EXIT .

La riga successiva è piuttosto semplice, tranne ${3:+;} . La sintassi ${X:+Y} significa "output Y se la variabile X non è impostata o nulla" . Quindi si espande ; se $3 è impostato, o niente altrimenti (se non ci fosse un gestore precedente per SIGNAL ).


No

Il meglio che si possa fare è eseguire più comandi da una singola trap per un determinato segnale, ma non si possono 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 trap correnti: dovresti catturare l'output standard da questo e analizzarlo per il segnale che desideri. Quindi, puoi aggiungere il tuo codice a ciò che era già lì - notando che il codice precedente probabilmente include 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.

È anche possibile utilizzare trap -p INT o trap -p 2 per stampare il trap per un segnale specifico.


Non mi piaceva dover giocare con queste manipolazioni di stringhe che sono confusionarie nel migliore dei casi, quindi mi è venuta in mente una cosa 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

Uso:

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

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

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 "[email protected]"
}

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 "[email protected]"; 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 "[email protected]"; 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 "[email protected]"; 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 "[email protected]"

  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 [email protected]
}

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
}

uso

cd ~/test
./test.sh

Produzione

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






bash-trap