web-dev-qa-db-ja.com

同じ信号に対する複数のbashトラップ

Bashでtrapコマンドを使用すると、指定した信号の以前のtrapが置き換えられます。

同じシグナルに対して複数のtrapを発生させる方法はありますか?

57
jes5199

編集:

私はその質問を誤解しているようです。答えは簡単です:

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"

これは、同じ関数によって処理されている他の追加の信号がある場合でも機能します。言い換えると、関数がすでに3つの信号を処理していたとしましょう。既存の1つを参照して2つ追加するだけで、さらに2つ追加できます(1つだけが上の引用符の内側に示されています)。

Bash> = 3.2を使用している場合は、次のようにして、信号を指定して関数を抽出できます。他の単一引用符が表示される可能性があるため、完全に堅牢ではないことに注意してください。

[[ $(trap -p SIGUSR1) =~ trap\ --\ \'([^\047]*)\'.* ]]
function_name=${BASH_REMATCH[1]}

その後、関数名などを使用する必要がある場合は、trapコマンドを最初から再構築できます。

29

技術的には、同じ信号に複数のトラップを設定することはできませんが、既存のトラップに追加できます。

  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
44
Richard Hansen

番号

できる最善の方法は、特定の信号に対して単一のtrapから複数のコマンドを実行することですが、単一の信号に対して複数の同時トラップを持つことはできません。例えば:

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

最初の行は、シグナル2(SIGINT)にトラップを設定します。 2行目は現在のトラップを出力します。これから標準出力をキャプチャして、必要な信号を解析する必要があります。次に、既存のコードにコードを追加できます。前のコードには「exit」操作が含まれている可能性が高いことに注意してください。トラップの3番目の呼び出しは、2/INTでトラップをクリアします。最後のものは、未解決のトラップがないことを示しています。

trap -p INTまたはtrap -p 2を使用して、特定の信号のトラップを出力することもできます。

11

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
}
6
WSimpson

別のオプションを次に示します。

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# 
3
Anonymoose

最高の時に混乱するこれらの文字列操作で遊ぶ必要がなかったので、次のようなものを思いつきました。

(明らかに、他の信号用に変更できます)

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
}
2
Oscar Byrne

このタスクを簡単に解決するための一連の関数を自分で作成しました。

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
1
Andry

同じトラップに複数のハンドラーを設定する方法はありませんが、同じハンドラーで複数の処理を実行できます。

同じことをする他のさまざまな答えで私が気に入らないのは、現在のトラップ関数を取得するための文字列操作の使用です。これを行うには、配列と引数の2つの簡単な方法があります。引数が最も信頼できるものですが、配列を最初に示します。

配列

配列を使用する場合、_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(Shellの単体テスト)をソースとして実行し、シグナルがEXITであるとします。 _trap -p EXIT_の出力は_trap -- '_osht_cleanup' EXIT_になるため、trapDecl割り当ては次のように置き換えられます。

_trapDecl=(trap -- '_osht_cleanup' EXIT)
_

括弧には通常の配列割り当てがあるため、trapDecltrap、_--_、_'_osht_cleanup'_およびEXITの4つの要素を持つ配列になります。

次に、現在のハンドラーを抽出します。これは次の行でインライン化できますが、説明のために、まずハンドラーを変数に割り当てました。その行を簡略化して、私は次のようにしています:_currentHandler="${array[@]:offset:length}"_、これはBashが要素lengthで始まるoffset要素を選択するために使用する構文です。 _0_からカウントを開始するため、数値_2_は_'_osht_cleanup'_になります。次に、_${#trapDecl[@]}_はtrapDecl内の要素の数で、この例では4です。不要な3つの要素、trap、_--_およびEXITがあるため、3を減算します。 offsetおよびlength引数で算術展開が既に実行されているため、その式の周囲で$(...)を使用する必要はありません。

最後の行はevalを実行します。これは、シェルがtrapの出力からの引用を解釈するために使用されます。その行でパラメーター置換を行うと、例では次のように展開されます。

_eval "trap -- 'your handler;''_osht_cleanup' EXIT"
_

中央の二重引用符(_''_)と混同しないでください。 Bashは、2つの引用符文字列が隣接している場合、それらを単に連結します。たとえば、_'1'"2"'3''4'_は、Bashによって_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_は1つの要素であり、_-fr_は単一の要素ではなく、_'rm -fr ...'_も1つの要素です。

しかし、currentHandlerは3つの要素(6-3 = 3)をすべて取得し、evalが実行されると、クォートが機能します。

議論

引数はすべての配列処理部分をスキップし、引用を正しくするためにevalを前に使用します。欠点は、bashの位置引数を置き換えることです。そのため、これは関数から行うのが最適です。しかし、これはコードです:

_eval "set -- $(trap -p SIGNAL)"
trap -- "your handler${3:+;}${3}" SIGNAL
_

最初の行は、位置引数を_trap -p SIGNAL_の出力に設定します。配列セクションの例を使用すると、_$1_はtrap、_$2_は_--_、_$3_は__osht_cleanup_になります(引用符なし) !)、および_$4_はEXITになります。

次の行は、_${3:+;}_を除いてかなり簡単です。 _${X:+Y}_構文は"変数Yが設定されていないかnullの場合、出力X"を意味します。したがって、_;_が設定されている場合は_$3_に展開され、それ以外の場合は何も展開されません(SIGNALの以前のハンドラーがない場合)。

1

Laurent Simontrap-addスクリプトのもう少し堅牢なバージョンを追加します。

  • '文字を含む、任意のコマンドをトラップとして使用できるようにします
  • Bashでのみ機能します。 bashパターンの代わりにsedを使用して書き換えることもできますが、これにより大幅に遅くなります。
  • それでも、サブシェルでトラップの不要な継承を引き起こすことに苦しんでいます。
trap-add () {
    local handler=$(trap -p "$2")
    handler=${handler/trap -- \'/}    # /- Strip `trap '...' SIGNAL` -> ...
    handler=${handler%\'*}            # \-
    handler=${handler//\'\\\'\'/\'}   # <- Unquote quoted quotes ('\'')
    trap "${handler} $1;" "$2"
}
0
kdb

それを行う簡単な方法

  1. すべての信号処理関数が同時にわかっている場合は、次で十分です( Jonathan で示されています)。
trap 'handler1;handler2;handler3' EXIT
  1. そうでなければ、留まるべき既存のハンドラーがある場合、新しいハンドラーは次のように簡単に追加できます。
trap "$( trap -p EXIT | cut -f2 -d \' );newHandler" EXIT
  1. 既存のハンドラーが存在するかどうかはわからないが、この場合にそれらを保持したい場合は、次のようにします。
 handlers="$( trap -p EXIT | cut -f2 -d \' )"
 trap "${handlers}${handlers:+;}newHandler" EXIT
  1. それはそのような関数で因数分解できます:
trap-add() {
    local sig="${2:?Signal required}"
    hdls="$( trap -p ${sig} | cut -f2 -d \' )";
    trap "${hdls}${hdls:+;}${1:?Handler required}" "${sig}"
}

export -f trap-add

使用法:

trap-add 'echo "Bye bye"' EXIT
trap-add 'echo "See you next time"' EXIT

Remark:これは、ハンドラーが関数名であるか、単純なコートを含まない単純な命令(単純なコートはcut -f2 -d \')。

0
Laurent Simon