IT이야기

동일한 신호에 대한 여러 bash 트랩

cyworld 2021. 4. 10. 09:43
반응형

동일한 신호에 대한 여러 bash 트랩


bash에서 "trap"명령을 사용하면 주어진 신호에 대한 이전 트랩이 대체됩니다.

동일한 신호에 대해 둘 이상의 트랩 발사 방법이 있습니까?


편집하다:

질문을 잘못 읽은 것 같습니다. 대답은 간단합니다.

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]}

그런 다음 함수 이름 등을 사용해야하는 경우 처음부터 트랩 명령을 다시 빌드 할 수 있습니다.


기술적으로 동일한 신호에 대해 여러 트랩을 설정할 수 없지만 기존 트랩에 추가 할 수 있습니다.

  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

아니

최선의 방법은 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특정 신호의 트랩을 인쇄 할 수 있습니다.


Richard Hansen의 대답이 마음에 들었지만 임베디드 기능은 신경 쓰지 않으므로 대안은 다음과 같습니다.

#===================================================================
# 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 "$@"; 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 "$@"
}

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

동일한 트랩에 대해 여러 처리기를 가질 수있는 방법은 없지만 동일한 처리기로 여러 작업을 수행 할 수 있습니다.

동일한 일을하는 다양한 다른 답변에서 내가 싫어하는 한 가지는 현재 트랩 기능을 얻기 위해 문자열 조작을 사용하는 것입니다. 이를 수행하는 두 가지 쉬운 방법이 있습니다. 배열과 인수입니다. 인수가 가장 신뢰할 수 있지만 먼저 배열을 보여 드리겠습니다.

배열

배열을 사용할 때를 trap -p SIGNAL반환 한다는 사실에 의존 trap -- ??? SIGNAL하므로의 값이 무엇이든 ???배열에 3 개의 단어가 더 있습니다.

따라서 다음을 수행 할 수 있습니다.

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가 lengthelement에서 시작하는 요소를 선택하는 데 사용하는 구문 offset입니다. 에서 계산을 시작하므로 0숫자 2는입니다 '_osht_cleanup'. 다음 ${#trapDecl[@]}은 내부 요소의 수이며 trapDecl예제에서는 4가됩니다. 당신은 당신이 원하지 않는 세 가지 요소가 있기 때문에 3을 빼기 : trap, --EXIT. $(...)산술 확장이 이미 offsetlength인수 에 대해 수행되기 때문에 해당 표현식 주위 에 사용할 필요가 없습니다 .

마지막 줄은를 수행 eval하는데, 이것은 쉘이의 출력에서 ​​인용을 해석하도록 사용됩니다 trap. 해당 행에서 매개 변수 대체를 수행하면 예제에서 다음과 같이 확장됩니다.

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

중간에있는 큰 따옴표 ( '') 로 혼동하지 마십시오 . Bash는 두 개의 따옴표 문자열이 서로 옆에 있으면 단순히 연결합니다. 예를 들어, Bash '1'"2"'3''4'1234의해 확장됩니다 . 또는, 더 재미있는 예를 제공하기 위해, 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. 배열 섹션에서 예제를 사용 $1trap, $2할 것이다 --, $3없을 것이다 _osht_cleanup(! 따옴표), 그리고 $4될 것입니다 EXIT.

다음 줄은 ${3:+;}. ${X:+Y}구문 수단 "출력 Y변수 경우 X해제 또는 널" . 따라서 ;if $3가 설정되어 있거나 그렇지 않으면 아무것도 확장 되지 않습니다 (에 대한 이전 처리기가없는 경우 SIGNAL).

참조 URL : https://stackoverflow.com/questions/3338030/multiple-bash-traps-for-the-same-signal

반응형