несколько ловушек для одного и того же сигнала - PullRequest
52 голосов
/ 26 июля 2010

Когда я использую команду «trap» в bash, предыдущая ловушка для данного сигнала заменяется.

Есть ли способ сделать несколько ловушек для одного и того же сигнала?

Ответы [ 8 ]

44 голосов
/ 02 сентября 2011

Технически вы не можете установить несколько ловушек для одного и того же сигнала, но вы можете добавить к существующей ловушке:

  1. Получить существующий код прерывания, используя trap -p
  2. Добавьте команду, разделенную точкой с запятой или новой строкой
  3. Установить ловушку на результат # 2

Вот функция 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 "$@"; 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

Пример использования:

trap_add 'echo "in trap DEBUG"' DEBUG
27 голосов
/ 26 июля 2010

Изменить:

Похоже, я неправильно понял вопрос. Ответ прост:

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 с нуля, если вам нужно использовать имя функции и т. Д.

11 голосов
/ 26 июля 2010
нет

Нет

Самое лучшее, что вы можете сделать, - это запустить несколько команд от одного trap для данного сигнала, но вы не можете иметь несколько одновременных прерываний для одного сигнала. Например:

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

Первая строка устанавливает ловушку для сигнала 2 (SIGINT). Во второй строке выводятся текущие ловушки - вам нужно будет захватить стандартный вывод этого сигнала и проанализировать его для нужного вам сигнала. Затем вы можете добавить свой код к тому, что уже было там, отметив, что предыдущий код, скорее всего, будет включать в себя операцию «выхода». Третий вызов ловушки очищает ловушку на 2 / INT. Последний показывает, что ловушек нет.

Вы также можете использовать trap -p INT или trap -p 2, чтобы напечатать ловушку для определенного сигнала.

6 голосов
/ 04 июня 2015

Мне понравился ответ Ричарда Хансена, но я не забочусь о встроенных функциях, поэтому альтернатива:

#===================================================================
# 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: /2973011/neskolko-lovushek-dlya-odnogo-i-togo-zhe-signala
#===================================================================
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
}
3 голосов
/ 19 января 2014

Вот еще один вариант:

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# 
2 голосов
/ 16 июня 2017

Мне не нравилось играть с этими струнными манипуляциями, которые в лучшем случае сбивают с толку, поэтому я придумал что-то вроде этого:

(очевидно, вы можете изменить его для других сигналов)

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
}
1 голос
/ 07 мая 2018

Мне был написан набор функций для себя, чтобы немного решить эту задачу удобным способом.

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
}

Использование

cd ~/test
./test.sh

выход

~ ~/test
111
popd
~/test
--
~ ~/test
222
exitting...
popd
~/test
0 голосов
/ 04 сентября 2018

Нет способа иметь несколько обработчиков для одной и той же ловушки, но один и тот же обработчик может делать несколько вещей.

Единственное, что мне не нравится в других ответах, делающих одно и то же, - это использование строковых манипуляций для получения текущей функции ловушки. Есть два простых способа сделать это: массивы и аргументы. Аргументы самые надежные, но я сначала покажу массивы.

Массивы

При использовании массивов вы полагаетесь на тот факт, что trap -p 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 массиву. В качестве примера предположим, что вы запустили это после получения источника 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} означает "output Y, если переменная X не установлена ​​или равна нулю" . Таким образом, он расширяется до ;, если установлено $3, или ничего другого (если ранее не было обработчика для SIGNAL).

...