I want to execute a trap command in "global" scope, but the signal will come from within function. Of course it is possible to declare the variable globally beforehand or use the -g
declare option. But in cases where I want to source on trap that's not very practicable as shown below:
#!/bin/bash
# ./variables
declare say=hello
declare -i times=4
and the actual script:
#!/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)
But without declare -g NAME
, thank you.
EDIT:
Is it possible to detach the kill -s USR1 $$
part completely from the current process, so that the signal comes from 'outside'?
I tried nohup
and disown
without success so far.
PS: @LL2 offered a solution using background process or coproc here: https://unix.stackexchange.com/a/518720/154557
But I am afraid the time sync issues pop up and make the code disproportionately complicated.
PPS: Actually @LL2 solution even works great with IPC, so I consider the question solved. A downside is that function arguments HAVE TO be processed within the sourced file if needed for further use in main scope, because of course symbols in the subshell are lost. Have a look into EDIT 2 for another solution coping this issue.
#!/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[@]}"
EDIT 2 (Proposal 3 Work in Progress):
Has anyone enough experience with bash custom builtins to point into a direction where a true solution can be achieved?
PS: Bash Loadable Builtins are super powerful and can most prabably be used to receive dbus method calls in main scope.
SOLUTION II (without bg nor coproc):
As it seems the signal always is triggered inside the scope of current execution line. Most probably there is another more sophisticated solution using custom bash builtins (loadable builtins which are pretty powerfull). I can even think of bash-dbus-builtins for IPC. Anyways, this solution using aliases can be usefull if one wants to tell self/another PID sleeping in the background to source some script on signal, without having to set the -g global flag in the sourced script ./variables, so the ./variables script can also be used exclusively within function scope also if necessary. As aliases cannot be exported to subshells, one would have to include the alias into an environment script referenced by BASH_ENV variable for example. Here I extended the original example with an IPC mechanism to make the objective of the use case clearer.
The variable store now also receives arguments
#!/bin/bash
# ./variables
declare say=hello
declare -i times=4
declare -a args=("$@")
First I prepare an environment script
#!/bin/bash
# ./env
shopt -s expand_aliases
alias source_ipc='source ./ipc'
alias source_ipc_rcv='source ./ipc_rcv'
and ensure to make the alias available in every bash script.
$> export BASH_ENV=./env
furthermore I need the actual IPC send- and reveive-mechanism using fifo
#!/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
plus ipc receive
#!/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
Now I can transparently trigger the source-signal across multiple processes
#!/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
From 'outside' the process namespace the signal can be triggered as follows. I assume that the Alias was already source by BASH_ENV reference.
$> ./trapsource & TSPID=$!; sleep 1; source_ipc $TSPID ./variables arg1 arg2; sleep 2; kill $TSPID