如何從函式發出“全域”bash範圍內的陷阱?
我想在“全域”範圍內執行陷阱命令,但信號將來自函式內部。當然,可以預先全域聲明變數或使用
-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
是否可以將 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 似乎在執行函式的上下文中執行陷阱程式碼。