web-dev-qa-db-fra.com

plusieurs pièges bash pour le même signal

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?

57
jes5199

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.

29
Dennis Williamson

Techniquement, vous ne pouvez pas définir plusieurs pièges pour le même signal, mais vous pouvez ajouter à un piège existant:

  1. Récupérez le code d'interruption existant à l'aide de trap -p
  2. Ajoutez votre commande, séparée par un point-virgule ou une nouvelle ligne
  3. Réglez le piège sur le résultat de # 2

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
44
Richard Hansen

Non

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.

11
Jonathan Leffler

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
}
6
WSimpson

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# 
3
Anonymoose

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
}
2
Oscar Byrne

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
1
Andry

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.

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

Arguments

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

1
Daniel C. Sobral

J'ajoute une version légèrement plus robuste de Laurent Simon 's trap-add script:

  • Permet d'utiliser des commandes arbitraires comme piège, y compris celles avec ' personnages
  • Fonctionne uniquement en bash; Il pourrait être réécrit avec sed au lieu de la substitution de modèle bash, mais cela le rendrait beaucoup plus lent.
  • Souffre toujours de provoquer un héritage indésirable des pièges dans les sous-coquilles.
trap-add () {
    local handler=$(trap -p "$2")
    handler=${handler/trap -- \'/}    # /- Strip `trap '...' SIGNAL` -> ...
    handler=${handler%\'*}            # \-
    handler=${handler//\'\\\'\'/\'}   # <- Unquote quoted quotes ('\'')
    trap "${handler} $1;" "$2"
}
0
kdb

Des façons simples de le faire

  1. Si toutes les fonctions de gestion du signal sont connues en même temps, alors ce qui suit est suffisant (a dit par Jonathan ):
trap 'handler1;handler2;handler3' EXIT
  1. Sinon, s'il existe des gestionnaires existants qui devraient rester, de nouveaux gestionnaires peuvent facilement être ajoutés comme suit:
trap "$( trap -p EXIT | cut -f2 -d \' );newHandler" EXIT
  1. Si vous ne savez pas s'il existe des gestionnaires mais que vous souhaitez les conserver dans ce cas, procédez comme suit:
 handlers="$( trap -p EXIT | cut -f2 -d \' )"
 trap "${handlers}${handlers:+;}newHandler" EXIT
  1. Il peut être factorisé dans une fonction comme celle-ci:
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 \').

0
Laurent Simon