più bash trap per lo stesso segnale
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?
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:
- Recupera il codice trap esistente usando
trap -p
- Aggiungi il tuo comando, separato da un punto e virgola o da una nuova riga
- 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 :
- 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. - 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. - Protezione dalla chiamata non da un contesto di funzione in caso di trap del segnale
RETURN
. - 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 funzionetkl_push_trap
- 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 funzionetkl_push_trap
. - Poiché il segnale
EXIT
non attiva il gestore della trap del segnaleRETURN
, il gestore della trap del segnaleEXIT
esegue automaticamente la configurazione almeno una volta per processo bash quando il gestore della trap del segnaleRETURN
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 segnaleEXIT
gestiscono automaticamente tutti i gestori di trapRETURN
prima di eseguire se stessi. - Il gestore di trap di segnale
RETURN
può ancora chiamare le funzionitkl_push_trap
etkl_pop_trap
per elaborare le trap di segnale nonRETURN
- Il gestore di trap di segnale
RETURN
può chiamare la funzionetkl_set_trap_postponed_exit
da entrambi i gestori di trap di segnaleEXIT
eRETURN
. Se viene chiamato dal gestore di trap di segnaleRETURN
, il gestore di trapEXIT
verrà chiamato dopo tutti i gestori di trap di segnaleRETURN
nel processo bash. Se viene chiamato dal gestore di trap di segnaleEXIT
, il gestore di trapEXIT
cambierà il codice di uscita dopo che è stato richiamato l'ultimo gestore di trap di segnaleEXIT
. - Accesso più rapido allo stack di trap come variabile globale invece di utilizzare gli operatori
(...)
o$(...)
che invoca un processo bash esterno. - Il comando
source
viene ignorato dal gestore di trap di segnaleRETURN
, quindi tutte le chiamate al comandosource
non invocheranno il codice utente di trap di segnaleRETURN
(contrassegnato in Pro, perché il gestore di trap di segnaleRETURN
deve essere chiamato solo dopo il ritorno dauna funzione in primo luogo e non da un'inclusione di script).
Contro :
- Non devi usare il comando
trap
integrato nel gestore passato alla funzionetkl_push_trap
poiché le funzionitkl_*_trap
lo usano internamente. - Non è necessario utilizzare il comando
exit
integrato nei gestori di segnaleEXIT
mentre il gestore di trap di segnaleEXIT
è in esecuzione.Altrimenti ciò lascerà il resto dei gestori di trap di segnaleRETURN
eEXIT
non eseguiti. Per modificare il codice di uscita dal gestoreEXIT
puoi usare la funzionetkl_set_trap_postponed_exit
per quello. - Non è necessario utilizzare il comando
return
integrato nel gestore di trap di segnaleRETURN
mentre il gestore di trap di segnaleRETURN
è in esecuzione.Altrimenti ciò lascerà il resto dei gestori di trap di segnaleRETURN
eEXIT
non eseguiti. - Tutte le chiamate alle funzioni
tkl_push_trap
etkl_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). - Devi sostituire tutti i comandi
trap
incorporati negli script nidificati o di terze parti contkl_*_trap
funzioni se stai già utilizzando la libreria. - Il comando
source
viene ignorato dal gestore della trap del segnaleRETURN
, quindi tutte le chiamate al comandosource
non invocheranno il codice utente della trappola del segnaleRETURN
(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
- Se tutte le funzioni di gestione del segnale sono note contemporaneamente, allora è sufficiente quanto segue (ha detto dal Gionata):
trap 'handler1;handler2;handler3' EXIT
- 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
- 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
- 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'"