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:
- Obtener el código de trampa existente usando la
trap -p
- Agregue su comando, separado por punto y coma o nueva línea
- 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.