web-dev-qa-db-ja.com

関数から「グローバル」bashスコープのトラップを通知する方法は?

「グローバル」スコープでトラップコマンドを実行したいのですが、信号は関数内から送信されます。もちろん、事前に変数をグローバルに宣言することも、-g宣言オプションを使用することもできます。しかし、以下に示すように、あまり実用的ではないトラップで調達したい場合:

#!/bin/bash
# ./variables
declare say=hello
declare -ri times 3

および実際のスクリプト:

#!/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 $$部分を現在のプロセスから完全に切り離して、信号が「外部」から来るようにすることは可能ですか?

Nohupdisownを試しましたが、今のところ成功していません。

編集2:真のソリューションを実現できる方向を示すのに十分なbashカスタムビルトインの経験はありますか?

2
Dominik Kummer
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自体は変数をローカルにしないので、トラップ内でreadonly timesを使用することもできると思います。

例えば。これにより、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
ilkkachu
#!/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スコープの範囲内ですが、それを変更する方法はないと思います。

1
Kusalananda

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)
0
LL3