Lorsque j'utilise la commande trap
dans bash, le précédent trap
pour le signal donné est remplacé.
Existe-t-il un moyen de faire plus d'un trap
feu pour le même signal?
Modifier:
Il semble que j'ai mal lu la question. La réponse est simple:
handler1 () { do_something; }
handler2 () { do_something_else; }
handler3 () { handler1; handler2; }
trap handler3 SIGNAL1 SIGNAL2 ...
Original:
Énumérez simplement plusieurs signaux à la fin de la commande:
trap function-name SIGNAL1 SIGNAL2 SIGNAL3 ...
Vous pouvez trouver la fonction associée à un signal particulier en utilisant trap -p
:
trap -p SIGINT
Notez qu'il répertorie chaque signal séparément même s'ils sont gérés par la même fonction.
Vous pouvez ajouter un signal supplémentaire donné à un signal connu en procédant comme suit:
eval "$(trap -p SIGUSR1) SIGUSR2"
Cela fonctionne même si d'autres signaux supplémentaires sont traités par la même fonction. En d'autres termes, disons qu'une fonction gérait déjà trois signaux - vous pouvez en ajouter deux de plus simplement en faisant référence à un existant et en en ajoutant deux de plus (où un seul est indiqué ci-dessus juste à l'intérieur des guillemets de fermeture).
Si vous utilisez Bash> = 3.2, vous pouvez faire quelque chose comme ça pour extraire la fonction donnée un signal. Notez qu'il n'est pas complètement robuste car d'autres guillemets simples pourraient apparaître.
[[ $(trap -p SIGUSR1) =~ trap\ --\ \'([^\047]*)\'.* ]]
function_name=${BASH_REMATCH[1]}
Ensuite, vous pouvez reconstruire votre commande trap à partir de zéro si vous avez besoin d'utiliser le nom de la fonction, etc.
Techniquement, vous ne pouvez pas définir plusieurs pièges pour le même signal, mais vous pouvez ajouter à un piège existant:
trap -p
Voici une fonction bash qui fait ce qui précède:
# 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
Exemple d'utilisation:
trap_add 'echo "in trap DEBUG"' DEBUG
Le mieux que vous puissiez faire est d'exécuter plusieurs commandes à partir d'un seul trap
pour un signal donné, mais vous ne pouvez pas avoir plusieurs interruptions simultanées pour un même signal. Par exemple:
$ trap "rm -f /tmp/xyz; exit 1" 2
$ trap
trap -- 'rm -f /tmp/xyz; exit 1' INT
$ trap 2
$ trap
$
La première ligne établit un piège sur le signal 2 (SIGINT). La deuxième ligne imprime les pièges actuels - vous devrez capturer la sortie standard à partir de cela et l'analyser pour le signal que vous voulez. Ensuite, vous pouvez ajouter votre code à ce qui était déjà là - en notant que le code précédent inclura très probablement une opération de "sortie". La troisième invocation de trap efface le piège sur 2/INT. Le dernier montre qu'il n'y a pas de pièges en suspens.
Vous pouvez aussi utiliser trap -p INT
ou trap -p 2
pour imprimer l'interruption pour un signal spécifique.
J'ai aimé la réponse de Richard Hansen, mais je ne me soucie pas des fonctions intégrées, donc une alternative est:
#===================================================================
# 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
}
Voici une autre 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#
Je n'aimais pas avoir à jouer avec ces manipulations de cordes qui prêtent à confusion dans le meilleur des cas, alors j'ai trouvé quelque chose comme ça:
(vous pouvez évidemment le modifier pour d'autres signaux)
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
}
On m'a écrit un ensemble de fonctions pour résoudre un peu cette tâche de manière pratique.
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
}
tilisation
cd ~/test
./test.sh
Sortie
~ ~/test
111
popd
~/test
--
~ ~/test
222
exitting...
popd
~/test
Il n'y a aucun moyen d'avoir plusieurs gestionnaires pour le même piège, mais le même gestionnaire peut faire plusieurs choses.
La seule chose que je n'aime pas dans les diverses autres réponses faisant la même chose est l'utilisation de la manipulation de chaînes pour obtenir la fonction de trap actuelle. Il existe deux façons simples de procéder: les tableaux et les arguments. Les arguments sont les plus fiables, mais je vais d'abord montrer les tableaux.
Lorsque vous utilisez des tableaux, vous comptez sur le fait que trap -p SIGNAL
Renvoie trap -- ??? SIGNAL
, Donc quelle que soit la valeur de ???
, Il y a trois autres mots dans le tableau.
Vous pouvez donc faire ceci:
declare -a trapDecl
trapDecl=($(trap -p SIGNAL))
currentHandler="${trapDecl[@]:2:${#trapDecl[@]} - 3}"
eval "trap -- 'your handler;'${currentHandler} SIGNAL"
Expliquons donc cela. Tout d'abord, la variable trapDecl
est déclarée sous forme de tableau. Si vous faites cela à l'intérieur d'une fonction, elle sera également locale, ce qui est pratique.
Ensuite, nous affectons la sortie de trap -p SIGNAL
Au tableau. Pour donner un exemple, disons que vous exécutez ceci après avoir obtenu osht (test unitaire pour Shell) et que le signal est EXIT
. La sortie de trap -p EXIT
Sera trap -- '_osht_cleanup' EXIT
, Donc l'affectation trapDecl
sera remplacée comme ceci:
trapDecl=(trap -- '_osht_cleanup' EXIT)
Les parenthèses sont affectées à un tableau normal, donc trapDecl
devient un tableau à quatre éléments: trap
, --
, '_osht_cleanup'
Et EXIT
.
Ensuite, nous extrayons le gestionnaire actuel - qui pourrait être inséré dans la ligne suivante, mais pour des raisons d'explication, je l'ai d'abord attribué à une variable. Simplifiant cette ligne, je fais ceci: currentHandler="${array[@]:offset:length}"
, Qui est la syntaxe utilisée par Bash pour dire choisir des éléments length
en commençant par l'élément offset
. Puisqu'il commence à compter à partir de 0
, Le nombre 2
Sera '_osht_cleanup'
. Ensuite, ${#trapDecl[@]}
Est le nombre d'éléments à l'intérieur de trapDecl
, qui sera 4 dans l'exemple. Vous soustrayez 3 car il y a trois éléments que vous ne voulez pas: trap
, --
Et EXIT
. Je n'ai pas besoin d'utiliser $(...)
autour de cette expression car l'expansion arithmétique est déjà effectuée sur les arguments offset
et length
.
La ligne finale effectue un eval
, qui est utilisé pour que le shell interprète les citations à partir de la sortie de trap
. Si nous effectuons une substitution de paramètres sur cette ligne, elle se développe comme suit dans l'exemple:
eval "trap -- 'your handler;''_osht_cleanup' EXIT"
Ne soyez pas dérouté par la citation double au milieu (''
). Bash concatène simplement deux chaînes de guillemets si elles sont côte à côte. Par exemple, '1'"2"'3''4'
Est étendu à 1234
Par Bash. Ou, pour donner un exemple plus intéressant, 1" "2
Est la même chose que "1 2"
. Donc, eval prend cette chaîne et l'évalue, ce qui équivaut à exécuter ceci:
trap -- 'your handler;''_osht_cleanup' EXIT
Et cela gérera les citations correctement, transformant tout entre --
Et EXIT
en un seul paramètre.
Pour donner un exemple plus complexe, je préfère un nettoyage de répertoire au gestionnaire osht, donc mon signal EXIT
a maintenant ceci:
trap -- 'rm -fr '\''/var/folders/7d/qthcbjz950775d6vn927lxwh0000gn/T/tmp.CmOubiwq'\'';_osht_cleanup' EXIT
Si vous affectez cela à trapDecl
, il aura la taille 6 en raison des espaces sur le gestionnaire. Autrement dit, 'rm
Est un élément, tout comme -fr
, Au lieu que 'rm -fr ...'
Soit un élément unique.
Mais currentHandler
obtiendra les trois éléments (6 - 3 = 3), et la citation fonctionnera lorsque eval
sera exécuté.
Les arguments sautent simplement toute la partie de gestion du tableau et utilisent eval
à l'avant pour obtenir le bon devis. L'inconvénient est que vous remplacez les arguments positionnels sur bash, il est donc préférable de le faire à partir d'une fonction. C'est le code, cependant:
eval "set -- $(trap -p SIGNAL)"
trap -- "your handler${3:+;}${3}" SIGNAL
La première ligne définira les arguments positionnels sur la sortie de trap -p SIGNAL
. En utilisant l'exemple de la section Tableaux, $1
Sera trap
, $2
Sera --
, $3
Sera _osht_cleanup
(pas de guillemets!) et $4
sera EXIT
.
La ligne suivante est assez simple, à l'exception de ${3:+;}
. La syntaxe ${X:+Y}
Signifie "sortie Y
si la variable X
est non définie ou nulle". Il se développe donc en ;
Si $3
Est défini, ou rien d'autre (s'il n'y avait pas de gestionnaire précédent pour SIGNAL
).
J'ajoute une version légèrement plus robuste de Laurent Simon 's trap-add
script:
'
personnagessed
au lieu de la substitution de modèle bash, mais cela le rendrait beaucoup plus lent.trap-add () {
local handler=$(trap -p "$2")
handler=${handler/trap -- \'/} # /- Strip `trap '...' SIGNAL` -> ...
handler=${handler%\'*} # \-
handler=${handler//\'\\\'\'/\'} # <- Unquote quoted quotes ('\'')
trap "${handler} $1;" "$2"
}
trap 'handler1;handler2;handler3' EXIT
trap "$( trap -p EXIT | cut -f2 -d \' );newHandler" EXIT
handlers="$( trap -p EXIT | cut -f2 -d \' )"
trap "${handlers}${handlers:+;}newHandler" EXIT
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
Remarque : Cela ne fonctionne que tant que les gestionnaires sont des noms de fonction, ou des instructions simples qui ne contiennent aucune cote simple (les cotes simples sont en conflit avec cut -f2 -d \'
).