我想在“全局”范围内执行陷阱命令,但信号将来自函数内部。当然,可以预先全局声明变量或使用-g
declare 选项。但是如果我想在陷阱上获取资源,这不是很实用,如下所示:
#!/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中的符号当然会丢失。查看EDIT 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 Loadable Builtins 非常强大,很可能用于接收主范围内的 dbus 方法调用。
解决方案二(没有 bg 也没有 coproc):
看起来信号总是在当前执行行的范围内触发。很可能还有另一种更复杂的解决方案,使用自定义 bash 内置函数(非常强大的可加载内置函数)。我什至可以想到 IPC 的 bash-dbus-builtins。无论如何,如果一个人想告诉自己/另一个在后台休眠的 PID 在信号源上获取一些脚本,而不必在源脚本 ./variables 中设置 -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
从进程名称空间的“外部”可以按如下方式触发信号。我假设别名已经是 BASH_ENV 参考的来源。
$> ./trapsource & TSPID=$!; sleep 1; source_ipc $TSPID ./variables arg1 arg2; sleep 2; kill $TSPID
不要
declare -i
在陷阱中使用,而是事先执行此操作,然后在陷阱中分配新值:我认为您甚至可以
readonly times
在陷阱中使用,因为readonly
它本身不会使变量成为本地变量。例如,这会打印 1、3、3,然后是修改只读变量的错误。
再说一次,如果它是你要修改的全局变量,为什么不使用
declare -g
?我认为(内置)
kill
从脚本本身发送的信号与另一个进程发送的信号之间没有区别。如您所见,Bash 似乎在运行函数的上下文中运行陷阱代码。如果您根本不使用
declare
,陷阱会将say
变量设置为全局范围内的字符串。尽管如此,陷阱仍然在范围内被调用sendSignal
,但我认为没有办法改变它。问题不在于您的信号来自“内部”,而是您的主脚本在它仍在函数范围内运行时接收到该信号。从“外部”发送信号是不够的,否则一个简单的子shell
(kill -SIGUSR1 $$)
就足够了。您还需要以某种方式发送它,让主脚本有机会从sourceVariables
函数返回并输入您希望trap
在其中运行的任何其他范围。假设是主要范围,如果你想让你的变量“全局”而不明确标记它们。对于您的示例代码,我想说:只需
sourceVariables
在后台运行该函数。这样,陷阱肯定会在主范围内运行。例如,以下代码(我认为)按照您的意图执行:
但我想您的下一个要求是您的实际
sourceVariables
功能(不是您目前向我们展示的功能)不能全部在后台运行。如果是这种情况,您还需要
kill
在脚本和脚本之间建立一些同步机制,以确保一切都在正确的时刻发生。可以有多种方法,最好的一种可能取决于您的实际应用。一个简单的使用
coproc
内置的,它需要 Bash v4+: