Когда я использую команду "trap" в bash, предыдущая ловушка для данного сигнала заменяется.
Есть ли способ сделать более одного ловушки для одного и того же сигнала?
Когда я использую команду "trap" в bash, предыдущая ловушка для данного сигнала заменяется.
Есть ли способ сделать более одного ловушки для одного и того же сигнала?
Редактировать:
Похоже, я неправильно понял вопрос. Ответ прост:
handler1 () { do_something; }
handler2 () { do_something_else; }
handler3 () { handler1; handler2; }
trap handler3 SIGNAL1 SIGNAL2 ...
Оригинал:
Просто перечислите несколько сигналов в конце команды:
trap function-name SIGNAL1 SIGNAL2 SIGNAL3 ...
Вы можете найти функцию, связанную с конкретным сигналом, используя trap -p
:
trap -p SIGINT
Обратите внимание, что в нем перечислены все сигналы отдельно, даже если они обрабатываются одной и той же функцией.
Вы можете добавить дополнительный сигнал к известному, выполнив это:
eval "$(trap -p SIGUSR1) SIGUSR2"
Это работает, даже если есть другие дополнительные сигналы, обрабатываемые той же функцией. Другими словами, допустим, что функция уже обрабатывает три сигнала - вы можете добавить еще два, просто сославшись на один существующий и добавив еще два (где только один показан выше непосредственно внутри заключительных кавычек).
Если вы используете Bash> = 3.2, вы можете сделать что-то вроде этого, чтобы извлечь функцию из заданного сигнала. Обратите внимание, что он не совсем устойчив, поскольку могут появиться другие одинарные кавычки.
[[ $(trap -p SIGUSR1) =~ trap\ --\ \'([^\047]*)\'.* ]]
function_name=${BASH_REMATCH[1]}
Затем вы можете перестроить команду trap с нуля, если вам нужно использовать имя функции и т.д.
Технически вы не можете установить несколько ловушек для одного и того же сигнала, но вы можете добавить в существующую ловушку:
trap -p
Вот функция bash, которая делает следующее:
# 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 "[email protected]"; 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 "[email protected]"; 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
Пример использования:
trap_add 'echo "in trap DEBUG"' DEBUG
О лучшем, что вы можете сделать, это запустить несколько команд из одного trap
для данного сигнала, но вы не можете иметь несколько параллельных ловушек для одного сигнала. Например:
$ trap "rm -f /tmp/xyz; exit 1" 2
$ trap
trap -- 'rm -f /tmp/xyz; exit 1' INT
$ trap 2
$ trap
$
Первая строка устанавливает ловушку в сигнале 2 (SIGINT). Вторая строка печатает текущие ловушки - вам нужно будет отобразить стандартный вывод из этого и проанализировать его для сигнала, который вы хотите. Затем вы можете добавить свой код к тому, что уже есть, отметив, что предыдущий код, скорее всего, будет включать операцию "exit". Третий вызов ловушки очищает ловушку от 2/INT. Последний показывает, что нет ловушек.
Вы также можете использовать trap -p INT
или trap -p 2
для печати ловушки для определенного сигнала.
Мне понравился ответ Ричарда Хансена, но мне не нужны встроенные функции, поэтому альтернатива такова:
#===================================================================
# 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 "[email protected]"; 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
}
Вот еще один вариант:
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
Использование:
$ on_exit date
$ on_exit 'echo "Goodbye from '\''`uname`'\''!"'
$ exit
exit
Sat Jan 18 18:31:49 PST 2014
Goodbye from 'FreeBSD'!
tap#
Мне не нравилось играть с этими строковыми манипуляциями, которые сбивают с толку в лучшие времена, поэтому я придумал что-то вроде этого:
(очевидно, вы можете изменить его для других сигналов)
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
}
Мне был написан набор функций для себя, чтобы немного решить эту задачу удобным способом.
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 "[email protected]"
}
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 "[email protected]"; 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 "[email protected]"; 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 "[email protected]"; 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 "[email protected]"
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 [email protected]
}
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
}
использование
cd ~/test
./test.sh
Выход
~ ~/test
111
popd
~/test
--
~ ~/test
222
exitting...
popd
~/test
Не существует способа иметь несколько обработчиков для одной и той же ловушки, но один и тот же обработчик может делать несколько вещей.
Единственное, что мне не нравится в других ответах, делающих одно и то же, - это использование строковых манипуляций для получения текущей функции ловушки. Есть два простых способа сделать это: массивы и аргументы. Аргументы самые надежные, но я сначала покажу массивы.
При использовании массивов вы полагаетесь на тот факт, что trap -p SIGNAL
возвращает trap --??? SIGNAL
trap --??? SIGNAL
, так какой бы ни была стоимость ???
, есть еще три слова в массиве.
Поэтому вы можете сделать это:
declare -a trapDecl
trapDecl=($(trap -p SIGNAL))
currentHandler="${trapDecl[@]:2:${#trapDecl[@]} - 3}"
eval "trap -- 'your handler;'${currentHandler} SIGNAL"
Итак, давайте объясним это. Во-первых, переменная trapDecl
объявляется как массив. Если вы делаете это внутри функции, она также будет локальной, что удобно.
Затем мы присваиваем trap -p SIGNAL
сигнал trap -p SIGNAL
. В качестве примера, допустим, что вы запустили это после получения источника osht (модульное тестирование для оболочки), и что сигнал является EXIT
. Выходом trap -p EXIT
будет trap -- '_osht_cleanup' EXIT
, поэтому назначение trapDecl
будет заменено следующим образом:
trapDecl=(trap -- '_osht_cleanup' EXIT)
В скобках указано обычное присвоение массива, поэтому trapDecl
становится массивом с четырьмя элементами: trap
, --
, '_osht_cleanup'
и EXIT
.
Затем мы извлекаем текущий обработчик - он может быть встроен в следующую строку, но для пояснения я сначала назначил его переменной. Упрощая эту строку, я делаю это: currentHandler="${array[@]:offset:length}"
, который является синтаксисом, используемым Bash для определения элементов length
выбора, начинающихся со offset
элемента. Так как он начинает считать с 0
, номер 2
будет '_osht_cleanup'
. Далее, ${#trapDecl[@]}
- это количество элементов внутри trapDecl
, которое в примере будет 4. Вы вычитаете 3, потому что есть три элемента, которые вам не нужны: trap
, --
и EXIT
. Мне не нужно использовать $(...)
вокруг этого выражения, потому что арифметическое расширение уже выполняется для аргументов offset
и length
.
Последняя строка выполняет eval
, который используется для того, чтобы оболочка интерпретировала кавычки из вывода trap
. Если мы сделаем подстановку параметров в этой строке, в примере она расширится до следующего:
eval "trap -- 'your handler;''_osht_cleanup' EXIT"
Не смущайтесь двойной кавычкой в середине (''
). Bash просто объединяет две строки кавычек, если они находятся рядом друг с другом. Например, '1'"2"'3''4'
расширена до 1234
Bash. Или, чтобы привести более интересный пример, 1" "2
- это то же самое, что и "1 2"
. Таким образом, eval берет эту строку и оценивает ее, что эквивалентно выполнению этого:
trap -- 'your handler;''_osht_cleanup' EXIT
И это будет правильно обрабатывать кавычки, превращая все между --
и EXIT
в один параметр.
Чтобы привести более сложный пример, я добавляю очистку каталога к обработчику osht, поэтому мой сигнал EXIT
теперь имеет следующее:
trap -- 'rm -fr '\''/var/folders/7d/qthcbjz950775d6vn927lxwh0000gn/T/tmp.CmOubiwq'\'';_osht_cleanup' EXIT
Если вы назначите это trapDecl
, он будет иметь размер 6 из-за пробелов в обработчике. Таким образом, 'rm
- это один элемент, как и -fr
, вместо 'rm -fr...'
- как один элемент.
Но currentHandler
получит все три элемента (6 - 3 = 3), и цитирование сработает при запуске eval
.
Аргументы просто пропускают всю часть обработки массива и используют eval
, чтобы получить правильное цитирование. Недостатком является то, что вы заменяете позиционные аргументы в bash, так что это лучше всего сделать из функции. Это код, хотя:
eval "set -- $(trap -p SIGNAL)"
trap -- "your handler${3:+;}${3}" SIGNAL
Первая строка установит позиционные аргументы на выход trap -p SIGNAL
. Используя пример из раздела Arrays, $1
будет trap
, $2
будет --
, $3
будет _osht_cleanup
(без кавычек!), А $4
будет EXIT
.
Следующая строка довольно проста, за исключением ${3:+;}
. Синтаксис ${X:+Y}
означает "вывод Y
если переменная X
не установлена или равна нулю". Так что расширяется до ;
если установлен $3
, или ничего другого (если не было предыдущего обработчика для SIGNAL
).