Eu quero executar um comando trap no escopo "global", mas o sinal virá de dentro da função. Claro que é possível declarar a variável globalmente de antemão ou usar a -g
opção declare. Mas nos casos em que eu quero fonte na armadilha, isso não é muito praticável, como mostrado abaixo:
#!/bin/bash
# ./variables
declare say=hello
declare -i times=4
e o script real:
#!/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)
Mas sem declare -g NAME
, obrigado.
EDITAR:
É possível separar kill -s USR1 $$
completamente a peça do processo atual, para que o sinal venha de 'fora'?
Tentei nohup
e disown
sem sucesso até agora.
PS: @LL2 ofereceu uma solução usando processo em segundo plano ou coproc aqui: https://unix.stackexchange.com/a/518720/154557
Mas temo que os problemas de sincronização de tempo apareçam e tornem o código desproporcionalmente complicado.
PPS: Na verdade, a solução @LL2 funciona muito bem com IPC, então considero a questão resolvida. Uma desvantagem é que os argumentos da função TÊM DE ser processados dentro do arquivo de origem, se necessário, para uso posterior no escopo principal, porque é claro que os símbolos no subshell são perdidos. Dê uma olhada no EDIT 2 para outra solução para lidar com esse problema.
#!/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 (Proposta 3 Trabalho em Andamento):
Alguém tem experiência suficiente com bash custom builtins para apontar em uma direção onde uma verdadeira solução pode ser alcançada?
PS: Bash Loadable Builtins são super poderosos e provavelmente podem ser usados para receber chamadas de método dbus no escopo principal.
SOLUÇÃO II (sem bg nem coproc):
Parece que o sinal sempre é acionado dentro do escopo da linha de execução atual. Muito provavelmente existe outra solução mais sofisticada usando builtins bash personalizados (builtins carregáveis que são bastante poderosos). Posso até pensar em bash-dbus-builtins para IPC. De qualquer forma, esta solução usando aliases pode ser útil se alguém quiser dizer a si mesmo/outro PID dormindo em segundo plano para fornecer algum script no sinal, sem ter que definir o sinalizador global -g no script de origem ./variables, então o ./ O script de variáveis também pode ser usado exclusivamente dentro do escopo da função, se necessário. Como os aliases não podem ser exportados para subshells, seria necessário incluir o alias em um script de ambiente referenciado pela variável BASH_ENV, por exemplo. Aqui estendi o exemplo original com um mecanismo IPC para tornar o objetivo do caso de uso mais claro.
A variável store agora também recebe argumentos
#!/bin/bash
# ./variables
declare say=hello
declare -i times=4
declare -a args=("$@")
Primeiro eu preparo um script de ambiente
#!/bin/bash
# ./env
shopt -s expand_aliases
alias source_ipc='source ./ipc'
alias source_ipc_rcv='source ./ipc_rcv'
e certifique-se de disponibilizar o alias em cada script bash.
$> export BASH_ENV=./env
além disso, preciso do mecanismo de envio e reveive IPC real usando 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
mais ipc receber
#!/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
Agora posso acionar de forma transparente o sinal de origem em vários processos
#!/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
De 'fora' do namespace do processo, o sinal pode ser acionado da seguinte maneira. Presumo que o Alias já foi fonte por referência BASH_ENV.
$> ./trapsource & TSPID=$!; sleep 1; source_ipc $TSPID ./variables arg1 arg2; sleep 2; kill $TSPID
Em vez de usar
declare -i
dentro do trap, faça isso antes e apenas atribua o novo valor no trap:Eu acho que você poderia até usar
readonly times
dentro do trap, já quereadonly
por si só não torna a variável local.Por exemplo, isso imprime 1, 3, 3 e, em seguida, um erro ao modificar uma variável somente leitura.
Então, novamente, se é a variável global que você deseja modificar, por que não usar
declare -g
?Eu não acho que haja diferença entre um sinal enviado por (o builtin)
kill
do próprio script e um enviado por outro processo. Como você viu, o Bash parece executar o código de trap no contexto da função em execução.Se você não usar
declare
nada, a armadilha definirá asay
variável para a string no escopo global. A armadilha ainda é chamada dentro dosendSignal
escopo, mas não acho que haja uma maneira de mudar isso.O problema não é que seu sinal vem de "dentro", mas sim que seu script principal recebe esse sinal enquanto ainda está sendo executado dentro do escopo da função. Não é suficiente enviar sinal de "fora", caso contrário, um simples subshell como
(kill -SIGUSR1 $$)
seria suficiente. Você também precisa enviá-lo de forma a dar ao script principal uma chance de retornar dosourceVariables
função e inserir qualquer outro escopo no qual você desejatrap
que seja executado. Supostamente o escopo principal, se você quiser tornar suas variáveis "globais" sem marcá-las explicitamente.Para o seu código de exemplo, eu diria: basta executar o
sourceVariables
função em segundo plano. Dessa forma, a armadilha certamente será executada no escopo principal.Por exemplo, o código a seguir faz como (eu acho) que você pretende:
Mas acho que seu próximo requisito será que sua função real
sourceVariables
(não a que você nos mostrou até agora) não deve ser executada em segundo plano.Se for esse o caso, você também precisa de algum mecanismo de sincronização entre
kill
o script e o script para garantir que tudo aconteça no momento certo. Pode haver várias maneiras e a melhor pode depender da sua aplicação real.Um simples usando o
coproc
built-in, que requer Bash v4+: