bash:導出的函式不可見,但變數是
多年來,我收集了一些 shell 和腳本引用的 bash 函式庫。為了減少導入樣板,我正在探索如何合理地將庫包含在腳本中的選項。
我的解決方案有兩部分——首先是導入配置(環境變數),然後是採購函式庫。
~/bash_envs : (配置)
export SOME_VAR=VALUE export SHELL_LIB=/path/to/library.sh # convenience funtion, so scripts who source env_vars file (ie this file) can # simply call it, instead of including the same block in each file themselves. function _load_common() { # import common library/functions: source $SHELL_LIB } export -f _load_common # marker var used to detect whether env vars (ie this file) have been loaded: export __ENV_VARS_LOADED_MARKER_VAR=loaded
現在從腳本執行以下程式碼:
if [[ "$__ENV_VARS_LOADED_MARKER_VAR" != loaded ]]; then # in our case $__ENV_VARS_LOADED_MARKER_VAR=loaded, ie this block is not executed USER_ENVS=/home/laur/bash_envs if [[ -r "$USER_ENVS" ]]; then source "$USER_ENVS" else echo -e "\n ERROR: env vars file [$USER_ENVS] not found! Abort." exit 1 fi fi _load_common
這會產生
_load_common: command not found
異常。這是為什麼?Note__ENV_VARS_LOADED_MARKER_VAR=loaded
很好地導出和可見,這就是為什麼沒有理由 source$USER_ENVS
;尚未_load_common()
找到函式,儘管它是從與 __ENV_VARS_LOADED_MARKER_VAR 相同的位置導出的。
問題
觀察:
$ bash -c 'foobar () { :; }; export -f foobar; dash -c env' |grep foobar $ bash -c 'foobar () { :; }; export -f foobar; bash -c env' |grep foobar BASH_FUNC_foobar%%=() { : $ bash -c 'foobar () { :; }; export -f foobar; ksh93 -c env' |grep foobar BASH_FUNC_foobar%%=() { : $ bash -c 'foobar () { :; }; export -f foobar; mksh -c env' |grep foobar $ bash -c 'foobar () { :; }; export -f foobar; zsh -c env' |grep foobar BASH_FUNC_foobar%%=() { : $ bash -c 'foobar () { :; }; export -f foobar; busybox sh -c env' |grep foobar BASH_FUNC_foobar%%=() { :
環境變數是 Unix 作業系統的一個特性。對它們的支持一直到核心:當一個程序呼叫另一個程序(使用
execve
系統呼叫)時,呼叫的參數之一是新程序的環境。sh 風格的 shell(dash、bash、ksh、…)中的內置命令
export
導致 shell 變數用作環境變數,該變數被傳輸到 shell 呼叫的程序。相反,當呼叫 shell 時,所有環境變數都成為該 shell 實例中的 shell 變數。導出的函式是 bash 功能。Bash 通過創建一個環境變數來“導出”一個函式,該變數的名稱是從函式的名稱派生的,其值是函式的主體(加上頭和尾)。您可以在上面看到環境變數的名稱是如何構造的:
BASH_FUNC_
然後是函式的名稱%%
。此名稱不是 shell 變數的有效名稱。回想一下,shell 在啟動時會將環境變數作為 shell 變數導入。當環境變數的名稱不是有效的 shell 變數時,不同的 shell 有不同的行為。有些將變數傳遞給它們的子程序(上圖:bash、ksh93、zsh、BusyBox),而另一些只將導出的 shell 變數傳遞給它們的子程序(上圖:dash、mksh),這有效地刪除了名稱不是有效的 shell 變數(ASCII 字母、數字和 的非空序列
_
)。最初,bash 使用了一個與函式同名的環境變數,這基本上可以避免這個問題。(僅在大多數情況下:函式名稱可以包含 shell 變數名稱中不允許的字元,例如
-
。)但這還有其他缺點,例如不允許導出 shell 變數和同名函式(以最後導出的為準)會覆蓋環境中的另一個)。至關重要的是,當 bash發現最初的實現導致了一個重大的安全漏洞時,它發生了變化。(另請參閱env x=’() { :;}; command’ bash 做了什麼以及為什麼它不安全?,什麼時候引入了 shellshock (CVE-2014-6271/7169) 錯誤,以及完整的更新檔是什麼?修復它?,Shellshock Bash 漏洞是如何被發現的?) 此更改的一個缺點是導出的函式不再通過某些程序,包括 dash 和 mksh。您的系統可能將破折號設置為
/bin/sh
. 這是一個非常受歡迎的選擇。/bin/sh
被使用了很多,所以很有可能sh
在呼叫路徑中的某個地方呼叫了從執行的原始 bashexport -f _load_common
實例到嘗試使用該函式的 bash 實例的呼叫。__ENV_VARS_LOADED_MARKER_VAR
通過,因為它有一個有效的變數名,但BASH_FUNC__load_common%%
沒有通過。解決方案
不要使用導出的函式。首先它們幾乎沒有用,對你來說它們完全沒用。導出函式的唯一優點是呼叫 bash 時不需要 bash 實例從某處讀取函式的定義,例如在腳本中定義函式並將其傳遞給從
find -exec
orxargs
或呼叫的 bash 實例parallel
。但是在您的情況下,您已經有了讀取函式定義的程式碼。所以只需無條件地閱讀函式定義。刪除export -f _load_common
,刪除__ENV_VARS_LOADED_MARKER_VAR
,然後呼叫source "$USER_ENVS"
.