関数から「グローバル」bash範囲トラップ信号をエクスポートする方法は?

関数から「グローバル」bash範囲トラップ信号をエクスポートする方法は?

「グローバル」範囲でトラップコマンドを実行したいのですが、信号は関数の内部から来ます。もちろん、変数は事前にグローバルに宣言することも、-g宣言オプションを使用して宣言することもできます。しかし、次のようにトラップのソースを取得したい場合は、あまり実用的ではありません。

#!/bin/bash
# ./variables
declare say=hello
declare -i times=4

実際のスクリプトは次のとおりです。

#!/bin/bash
# ./trapsource

setTraps () {
  trap 'echo trap called in scope ${FUNCNAME[@]}; source ./variables' SIGUSR1
}

sourceVariables () {
  kill -SIGUSR1 $$
}

setTraps
sourceVariables
echo I want you to $say \"hello\" $times times.
printf "$say\n%.0s" $(seq 1 $times)

しかし、いいえdeclare -g NAME、ありがとう。

編集する:

kill -s USR1 $$信号が「外部」から出るように、現在のプロセスからこの部分を完全に分離することは可能ですか?

私は試しましたが、nohupこれdisownまで成功しませんでした。

PS:@LL2は、ここでバックグラウンドプロセスまたはcoprocを使用するソリューションを提供します。https://unix.stackexchange.com/a/518720/154557 しかし、時間同期の問題が発生し、コードが非常に複雑になるか心配です。

PPS:実際、@LL2ソリューションはIPCでもうまく機能するため、この問題は解決されているようです。欠点は、デフォルトのスコープで追加の使用が必要な場合はサブシェルのシンボルが失われるため、関数パラメータをソースファイルで処理する必要があることです。見て編集2この問題に対する別の解決策。

#!/bin/bash
# ./trapsource
set -x  # <-- set debugging to see what happens

setTraps () {
  trap 'declare IPC=./ipc.fifo; \
    [ -p $IPC ] \
    && exec {IPCFD}<>$IPC \
    && { read -a ARGS <&$IPCFD; eval "exec $IPCFD>&-"; } \
    && source "${ARGS[@]}" \
    && IPC_RCV=true' SIGUSR1
}

sourceVariables () {
  declare IPC=./ipc.fifo
  declare IPC_RCV=false
  [ ! -p $IPC ] \
  && mkfifo $IPC
  [ $# -gt 0 ] \
  && exec {IPCFD}<>$IPC \
  && echo "${@:2}" >&$IPCFD \
  && eval "exec $IPCFD>&-" \
  && kill -s USR1 $1
}

test () {
  sourceVariables "$@" &
}

setTraps
test $$ ./variables a b c
while ! [ $say ] ; do :; done  # <-- careful: cpu-intensive loop, only for 
demonstration
echo I want you to $say \"hello\" $times times.
printf "$say\n%.0s" $(seq 1 $times)
printf "'%s' " "${args[@]}"

編集2(提案3進行中):

実際の解決策を達成するための方向性を教えるのに十分なbashカスタム組み込み機能の経験がある人はいますか?

PS:Bashのロード可能な組み込み機能は非常に強力で、デフォルトの範囲でdbusメソッド呼び出しを受け取るために最もよく使用されます。

ソリューションII(bgまたはcoprocなし):

信号は常に現在の実行ラインの範囲内で発生するようです。おそらく、カスタムbash組み込み機能(非常に強力なロード可能な組み込み機能)を使用するより複雑な別の解決策があります。 IPC用bash-dbus-builtinsも考えられます。とにかくエイリアスを使用するソリューションは、ソーススクリプトで -g グローバルフラグ ./variable を設定しなくても、シグナルの一部のスクリプトを取得するためにバックグラウンドで眠っている自分/他の PID に指示する場合に便利です。したがって、./変数必要に応じて、スクリプトを関数の範囲内で具体的に使用することもできます。エイリアスはサブシェルにエクスポートできないため、BASH_ENV変数によって参照される環境スクリプトにエイリアスを含める必要があります。ここでは、ユースケースの目標をより明確にするために、IPCメカニズムを使用して元の例を拡張しました。

これで、変数リポジトリのパラメータも許可されます。

#!/bin/bash
# ./variables
declare say=hello
declare -i times=4
declare -a args=("$@")

まず、環境スクリプトを準備します。

#!/bin/bash
# ./env
shopt -s expand_aliases
alias source_ipc='source ./ipc'
alias source_ipc_rcv='source ./ipc_rcv'

そして、すべてのbashスクリプトでエイリアスが利用可能であることを確認してください。

$> export BASH_ENV=./env

また、fifo を使用する実際の IPC 送受信メカニズムが必要です。

#!/bin/bash
# ./ipc

declare IPC=./ipc.fifo
declare IPC_RCV=false
[ ! -p $IPC ] \
&& mkfifo $IPC
[ $# -gt 0 ] \
&& exec {IPCFD}<>$IPC \
&& echo "${@:2}" >&$IPCFD \
&& eval "exec $IPCFD>&-" \
&& kill -s USR1 $1

受信するにはIPCを追加してください。

#!/bin/bash
# ./ipc_rcv

declare IPC=./ipc.fifo
[ -p $IPC ] \
&& exec {IPCFD}<>$IPC \
&& { read -a ARGS <&$IPCFD; eval "exec $IPCFD>&-"; } \
&& source "${ARGS[@]}" \
&& IPC_RCV=true

これで、複数のプロセスにわたってソース信号を透過的にトリガーできます。

#!/bin/bash
# ./trapsource u+x

trap 'source_ipc_rcv' SIGUSR1

log="$0.log"
err="$0.err.log"
exec 1>$log
exec 2>$err

# test inside script
source_ipc $$ ./variables a b c

while true; do
  echo I want you to \"$say\" $times times.
  if $IPC_RCV; then
    printf "$say\n%.0s" $(seq 1 $times)
    printf "'%s' " "${args[@]}"
  fi
  sleep 1
done

プロセス名前空間「外部」では、次のように信号がトリガーされることがあります。 BASH_ENV参照ですでにAliasを提供しているとします。

$> ./trapsource & TSPID=$!; sleep 1; source_ipc $TSPID ./variables arg1 arg2; sleep 2; kill $TSPID

答え1

trap 'echo trap called in scope ${FUNCNAME[@]}; declare say=hello; declare -ri times=3' SIGUSR1

declare -iトラップで使用するのではなく、事前にこれを実行してから、トラップに新しい値を割り当てます。

declare -i times=999
trap 'times=3' USR1

変数自体をローカルにしないreadonly timesので、トラップで使用することもできると思います。readonly

たとえば、これを行うと1、3、3が印刷され、読み取り専用変数を変更するとエラーが発生します。

#!/bin/bash
trap 'readonly num=3' USR1
sub() {
        kill -USR1 $$
        echo "$num"
}

declare -i num=999
num=1
echo "$num"
sub
echo "$num"
num=1234

それでは、変更したい項目がグローバル変数であれば、declare -gそれを使用するのはどうでしょうか?

kill -s USR1 $$信号が「外部」から出るように、現在のプロセスからこの部分を完全に分離することは可能ですか?

killスクリプト自体(組み込み)から送信される信号と他のプロセスで送信される信号との間に違いはないと思います。ご覧のとおり、Bashは実行中の関数のコンテキストでトラップコードを実行しているようです。

答え2

信号が「外部」から出るように、現在のプロセスからkill -s USR1 $ $部分を完全に分離できますか?

問題は、信号が「内部的に」来るのではなく、基本スクリプトが関数の範囲内で実行されている間に信号を受信することです。 「外部」から信号を送るだけでは不十分です。それ以外の場合は、単純なサブシェルで(kill -SIGUSR1 $$)十分です。また、基本スクリプトが関数から戻り、実行したいsourceVariables他の範囲を入力する機会を提供する方法でそれを送信する必要があります。trap変数を明示的に表示せずに「グローバル」として指定する場合は、デフォルトの範囲を想定します。

あなたのサンプルコードでは、次のように言いたいと思います。sourceVariables バックグラウンドで関数を実行します。これにより、トラップは確実にメインスコープで実行されます。

たとえば、次のコードは意図したとおりに実行されます。

#!/bin/bash

set -x  # <-- set debugging to see what happens

setTraps () {
  trap 'echo trap called in scope ${FUNCNAME[@]}; source ./variables' SIGUSR1
}

sourceVariables () {
  kill -SIGUSR1 $$
}

setTraps
sourceVariables &
while ! [ $say ] ; do :; done  # <-- careful: cpu-intensive loop, only for demonstration
echo I want you to $say \"hello\" $times times.
printf "$say\n%.0s" $(seq 1 $times)

しかし、私の考えでは、あなたの次の要求はあなたになると思います実際 sourceVariables 機能(これまでに示した機能ではない)はすべてバックグラウンドで実行してはいけません。

もしそうならkill 、すべてのことがタイムリーに行われるように、スクリプトとスクリプト間の同期メカニズムも必要です。いくつかのアプローチがあり、最善のアプローチは実際のアプリケーションによって異なります。

coproc組み込み関数を使用する簡単な方法では、Bash v4+が必要です。

#!/bin/bash

set -x  # <-- set debugging to see what happens

setTraps () {
  trap 'echo trap called in scope ${FUNCNAME[@]}; source ./variables' USR1
}

sourceVariables () {
  # Do interesting stuff

  coproc { read ; kill -USR1 $$ ; }  # run a coprocess in background
  # the coprocess starts by waiting on `read`, which serves as a "go-ahead notification"
  # from the script when this latter is ready to receive the signal

  # Do yet more interesting stuff
}

setTraps
sourceVariables

# do even more stuff not yet ready for USR1

echo >&${COPROC[1]}  # notify the coprocess that we're now ready to receive the signal
while ! [ $say ] ; do :; done  # <-- careful: cpu-intensive loop, only for demonstration
echo I want you to $say \"hello\" $times times.
printf "$say\n%.0s" $(seq 1 $times)

答え3

#!/bin/bash

setTraps () {
    trap 'echo trap called in scope ${FUNCNAME[@]}; say=hello' USR1
}

sendSignal () {
    kill -s USR1 "$$"
}

setTraps
sendSignal
printf 'I want you to %s "hello"\n' "$say"

まったく使用しない場合、declareトラップはsay変数をグローバル範囲の文字列に設定します。罠が残っていると言う範囲内にある間にsendSignalこれを変更する方法はありません。

関連情報