我想在“全局”范围内执行陷阱命令,但信号将来自函数内部。当然,可以预先全局声明变量或使用-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配合使用,所以我认为这个问题已经解决了。缺点是,如果需要在主作用域中进一步使用,函数参数必须在源文件中进行处理,因为子 shell 中的符号当然会丢失。看看编辑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。不管怎样,如果你想告诉自己/另一个在后台休眠的PID在信号上获取某些脚本,而不必在源脚本中设置-g全局标志./变量,那么使用别名的解决方案可能会很有用,因此./如有必要,变量脚本也可以在函数范围内专门使用。由于别名无法导出到子 shell,因此必须将别名包含到 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
从进程命名空间的“外部”,可以按如下方式触发信号。我假设 Alias 已经由 BASH_ENV 引用提供。
$> ./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 $$部分完全与当前进程分离,以便信号来自“外部”?
问题不在于您的信号来自“内部”,而在于您的主脚本仍在函数范围内运行时接收到该信号。从“外部”发送信号是不够的,否则一个简单的子 shell(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
,但我认为没有办法改变这一点。