trap bash bash-trap

bash - trap command in unix



múltiples trampas bash para la misma señal (8)

Cuando uso el comando "trap" en bash, la trampa anterior para la señal dada es reemplazada.

¿Hay alguna forma de hacer más de una trampa de fuego para la misma señal?


No

Lo mejor que se puede hacer es ejecutar múltiples comandos desde una sola trap para una señal dada, pero no puede tener múltiples trampas simultáneas para una sola señal. Por ejemplo:

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

La primera línea establece una trampa en la señal 2 (SIGINT). La segunda línea imprime las trampas actuales: deberá capturar la salida estándar de esta y analizarla para la señal que desee. Luego, puede agregar su código a lo que ya estaba allí, señalando que el código anterior probablemente incluirá una operación de ''salida''. La tercera invocación de trampa despeja la trampa en 2 / INT. El último muestra que no hay trampas pendientes.

También puede usar trap -p INT o trap -p 2 para imprimir la trampa para una señal específica.


Aquí hay otra opción:

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#


Me gustó la respuesta de Richard Hansen, pero no me interesan las funciones integradas, así que una alternativa es:

#=================================================================== # 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://.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 }


Me han escrito un conjunto de funciones para resolver un poco esta tarea de una manera 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 "$@" } 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 }

Uso

cd ~/test ./test.sh

Salida

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


No hay forma de tener múltiples controladores para la misma trampa, pero el mismo controlador puede hacer varias cosas.

Lo único que no me gusta de las otras respuestas que hacen lo mismo es el uso de la manipulación de cuerdas para llegar a la función de trampa actual. Hay dos maneras sencillas de hacerlo: matrices y argumentos. Arguments es el más confiable, pero primero mostraré los arrays.

Arrays

Al usar matrices, confías en el hecho de que la trap -p SIGNAL devuelve una trap -- ??? SIGNAL trap -- ??? SIGNAL , ¿cuál es el valor de ??? , hay tres palabras más en la matriz.

Por lo tanto, puedes hacer esto:

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

Entonces, expliquemos esto. Primero, la variable trapDecl se declara como una matriz. Si haces esto dentro de una función, también será local, lo cual es conveniente.

A continuación, asignamos la salida de la trap -p SIGNAL a la matriz. Para dar un ejemplo, digamos que está ejecutando esto después de haber obtenido el origen de osht (prueba de unidad para el shell), y que la señal es EXIT . El resultado de la trap -p EXIT será trap -- ''_osht_cleanup'' EXIT , por lo que la asignación trapDecl se sustituirá así:

trapDecl=(trap -- ''_osht_cleanup'' EXIT)

Los paréntesis hay una asignación de matriz normal, por lo que trapDecl convierte en una matriz con cuatro elementos: trap , -- , ''_osht_cleanup'' y EXIT .

A continuación, extraemos el controlador actual, que podría incluirse en la línea siguiente, pero, por razones de explicación, primero lo asigné a una variable. Simplificando esa línea, estoy haciendo esto: currentHandler="${array[@]:offset:length}" , que es la sintaxis utilizada por Bash para decir elementos de length selección comenzando en el offset elemento. Como comienza a contar desde 0 , el número 2 será ''_osht_cleanup'' . A continuación, ${#trapDecl[@]} es la cantidad de elementos dentro de trapDecl , que será 4 en el ejemplo. Resta 3 porque hay tres elementos que no quiere: trap , -- y EXIT . No necesito usar $(...) alrededor de esa expresión porque la expansión aritmética ya se realiza en los argumentos de offset y length .

La línea final realiza una eval , que se utiliza para que el intérprete interprete las citas de la salida de la trap . Si hacemos la sustitución de parámetros en esa línea, se expande a lo siguiente en el ejemplo:

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

No te confundas con la comilla doble en el medio ( '''' ). Bash simplemente concatena dos cadenas de comillas si están una al lado de la otra. Por ejemplo, ''1''"2"''3''''4'' se expande a 1234 por Bash. O, para dar un ejemplo más interesante, 1" "2 es lo mismo que "1 2" . Entonces eval toma esa cadena y la evalúa, lo que es equivalente a ejecutar esto:

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

Y eso manejará las citas correctamente, convirtiendo todo entre -- y EXIT en un solo parámetro.

Para dar un ejemplo más complejo, antepongo una limpieza de directorio al manejador osht, así que mi señal EXIT ahora tiene esto:

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

Si asigna eso a trapDecl , tendrá el tamaño 6 debido a los espacios en el controlador. Es decir, ''rm es un elemento, y también lo es -fr , en lugar de ''rm -fr ...'' es un elemento único.

Pero currentHandler obtendrá los tres elementos (6 - 3 = 3), y las citas funcionarán cuando se ejecute eval .

Argumentos

Arguments simplemente omite toda la parte de manejo de la matriz y usa eval por adelantado para obtener la cotización correcta. La desventaja es que reemplazas los argumentos posicionales en bash, por lo que esto se hace mejor desde una función. Este es el código, sin embargo:

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

La primera línea establecerá los argumentos posicionales a la salida de la trap -p SIGNAL . Usando el ejemplo de la sección Arrays, $1 será trap , $2 será -- , $3 será _osht_cleanup (¡sin comillas!), Y $4 será EXIT .

La siguiente línea es bastante simple, excepto ${3:+;} . La sintaxis ${X:+Y} significa "salida Y si la variable X está desactivada o nula" . Entonces se expande a ; si se establece $3 , o nada de lo contrario (si no había un controlador anterior para SIGNAL ).


No me gustaba jugar con estas manipulaciones de cadena que son confusas en el mejor de los casos, así que se me ocurrió algo como esto:

(obviamente, puedes modificarlo para otras señales)

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 }


Técnicamente no puedes configurar múltiples trampas para la misma señal, pero puedes agregarlas a una trampa existente:

  1. Obtener el código de trampa existente usando la trap -p
  2. Agregue su comando, separado por punto y coma o nueva línea
  3. Establezca la trampa en el resultado de # 2

Aquí hay una función bash que hace lo anterior:

# 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

Ejemplo de uso:

trap_add ''echo "in trap DEBUG"'' DEBUG


Editar:

Parece que he leído mal la pregunta. La respuesta es simple:

handler1 () { do_something; } handler2 () { do_something_else; } handler3 () { handler1; handler2; } trap handler3 SIGNAL1 SIGNAL2 ...

Original:

Simplemente liste múltiples señales al final del comando:

trap function-name SIGNAL1 SIGNAL2 SIGNAL3 ...

Puede encontrar la función asociada a una señal particular usando trap -p :

trap -p SIGINT

Tenga en cuenta que enumera cada señal por separado, incluso si están manejadas por la misma función.

Puede agregar una señal adicional dada una conocida al hacer esto:

eval "$(trap -p SIGUSR1) SIGUSR2"

Esto funciona incluso si hay otras señales adicionales procesadas por la misma función. En otras palabras, digamos que una función ya maneja tres señales: puede agregar dos más simplemente refiriéndose a una existente y agregando dos más (donde solo una se muestra arriba justo dentro de las comillas de cierre).

Si está utilizando Bash> = 3.2, puede hacer algo como esto para extraer la función dada una señal. Tenga en cuenta que no es completamente robusto porque podrían aparecer otras comillas simples.

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

Luego, podría reconstruir su comando trap desde cero si necesitara usar el nombre de la función, etc.