Подтвердить что ты не робот

Несколько ловушек bash для одного и того же сигнала

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

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

4b9b3361

Ответ 1

Редактировать:

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

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

Ответ 2

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

  • Извлеките существующий код ловушки с помощью trap -p
  • Добавьте команду, разделенную точкой с запятой или новой строкой
  • Установите ловушку в результат # 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 "[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

Ответ 3

Нет

О лучшем, что вы можете сделать, это запустить несколько команд из одного 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 для печати ловушки для определенного сигнала.

Ответ 4

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

#===================================================================
# 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
}

Ответ 5

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

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# 

Ответ 6

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

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

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
}

Ответ 7

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

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

Ответ 8

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

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

Массивы

При использовании массивов вы полагаетесь на тот факт, что 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).