Bash

如何從函式發出“全域”bash範圍內的陷阱?

  • May 28, 2021

我想在“全域”範圍內執行陷阱命令,但信號將來自函式內部。當然,可以預先全域聲明變數或使用-gdeclare 選項。但是如果我想在陷阱上獲取資源,這不是很實用,如下所示:

#!/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

是否可以將 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)
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 似乎在執行函式的上下文中執行陷阱程式碼。

引用自:https://unix.stackexchange.com/questions/500894