Linux shell變數在子程序不見:assignment與export

在終端設了變數,接著啟動腳本卻讀不到,常見原因是你只做了shell變數賦值,沒有把它放進子程序的環境。assignment與export各有作用:前者讓目前shell保存一個值,後者把該變數標記為輸出,讓後續啟動的程序收到它。先分清誰設定、誰讀取,比反覆把值寫進不同設定檔更容易找到問題。

目前shell有值,子shell未必有

以下案例使用專屬變數WN_SAMPLE,避免改動PATH或其他既有設定。先unset清掉這個名字,建立目前shell的值,再啟動新的bash讀取。子shell的參數展開用單引號包住,確保它在子shell裡執行;若改用雙引號,外層shell可能先把值展開,輸出看似成功,實際並沒有證明子程序收到了環境變數。

unset WN_SAMPLE
WN_SAMPLE=parent
printf "parent-before=%s\n" "$WN_SAMPLE"
bash -c 'printf "unexported-child=%s\n" "${WN_SAMPLE-unset}"'

在Ubuntu 24.04、GNU Bash 5.2.21的自有隔離案例中,目前shell輸出parent-before=parent,子shell輸出unexported-child=unset。這裡unset是取不到變數時由展開語法提供的文字,不是子shell真的建立了名為unset的值。新的bash沒有拿到尚未export的WN_SAMPLE,因此兩個結果可以同時成立。

export讓後續子程序收到環境值

export WN_SAMPLE
bash -c 'printf "exported-child=%s\n" "$WN_SAMPLE"'

這次子shell輸出exported-child=parent,因為WN_SAMPLE已被標記輸出。你也可以用export WN_SAMPLE=parent一次設定並輸出,但不要把兩種寫法的差異誤認為字串值的差異。關鍵是export屬性,而不是變數剛好叫什麼名字。已經輸出的變數再賦新值,後續程序會收到新的值;已經啟動的程序則不是這次操作的接收對象。

程序啟動時收到一份環境,而不是持續連到父shell變數的共享欄位。父shell之後改值,不能因此推論先前啟動的服務已更新。若網站程式讀的是啟動時的環境,修改終端中的值與正式服務讀到的新值之間還有自己的啟動流程。本案例只啟動短生命週期的bash,沒有改服務設定、重啟程序或測試正式站。

子程序改值,不會回寫父shell

bash -c 'WN_SAMPLE=child; printf "child-changed=%s\n" "$WN_SAMPLE"'
printf "parent-after=%s\n" "$WN_SAMPLE"

隔離案例的輸出分別是child-changed=child、parent-after=parent。子shell可以改自己的變數,但它的修改不會回流成父shell的新值。export也不會改變這個方向:它負責提供啟動環境,不是雙向同步機制。如果腳本需要把計算結果交回呼叫者,必須另用輸出、檔案或你設計的通訊方式,而不是只在子shell裡賦值。

同樣地,子程序收到的環境不代表它會再次把所有新變數傳給更下一層程序。若在子shell裡建立了另一個未輸出的變數,仍需分清該子shell自己的變數與它輸出的環境。排查多層腳本時,每一層都看誰啟動誰、值在哪裡設定,以及該名字是否被export,不要把最外層設定成功當成所有層都一定收到。

只給一個指令的暫時值

WN_SAMPLE=temporary bash -c 'printf "one-command-child=%s\n" "$WN_SAMPLE"'
printf "parent-final=%s\n" "$WN_SAMPLE"

這次one-command-child=temporary,但parent-final仍是parent。放在外部指令前面的賦值,可供該次指令使用,不必把父shell原值永久改成temporary。這很適合只想測一次程式對某個設定的反應。注意這裡接的是外部bash指令;shell特殊內建、函式或其他語法的環境與賦值規則不應一概套用。本篇只驗證所列的外部指令形式。

如果值含空白,仍要正確引用,例如WN_SAMPLE=”two words” bash -c後面的指令。引用控制shell如何把文字分成參數,export控制變數是否進入環境,兩者處理不同問題。收到值卻因未引用而分裂成多個參數,與完全沒有收到環境值,是兩種不同故障。先印出限定變數的實際值與參數,再決定要修哪一層。

在真正啟動程式的位置檢查

互動終端、排程與服務可能由不同程序啟動。你在某個終端export成功,只能證明該shell之後啟動的子程序能收到值,不能保證另一個排程或服務也繼承同一份環境。先找出實際啟動鏈,再在自己的測試程序中讀取需要的名字。不要為了排查一個變數就把整份環境公開貼出,因為其他名字可能包含與本問題無關的資料。

Bash啟動檔也與登入、互動等模式有關。腳本用bash -c啟動與你開終端後手動操作不完全相同;不能假設某個互動啟動檔一定會被所有程序讀取。本篇用乾淨的專屬變數加明確賦值,讓結果來自assignment與export,而不是依賴使用者既有啟動檔。若自己的環境出現不同結果,先看是否有啟動檔或程式內部再次設定同名值。

不要把執行腳本與source混成同一件事

執行一支腳本通常由另一個shell程序處理,腳本裡改變自己的變數,不能直接改回呼叫它的父shell。Bash的source或點指令則是在目前shell環境中讀取檔案,作用範圍不同。若為了讓值留下來而把原本執行腳本改成source,檔案中的其他命令也會在目前shell生效,不能只把它當成export的另一種寫法。應先確認檔案就是設計成可被讀入的設定,再選擇這種方式。本例沒有讀入其他檔案,也沒有用source改父shell。

也要分清空字串與未設定。${WN_SAMPLE-unset}只有變數未設定時才用替代文字;若變數存在但值是空字串,輸出仍是空。另一種帶冒號的展開形式會把空值也納入替代條件,兩者不能在排查時隨意交換。若你的程式把空字串視為有效設定,就應保留這個差別。本案例先unset再賦非空字串,讓未輸出與已輸出的結果更容易辨認,沒有把所有空值處理策略套給應用程式。

最後,export清單只是目前shell的狀態。另開一個與它沒有父子啟動關係的終端,不會自動得到你剛剛修改的值。若需要長期固定設定,要放到實際啟動程序使用的設定來源,再按該環境的程序管理方式生效。這一步超出單次shell實驗的範圍;先確認傳遞機制,才決定適合自己的設定位置。

用小案例確認傳遞方向

完整案例按順序得到五個關鍵結果:未輸出的子shell取不到值、export後收到parent、子shell改成child、父shell仍保留parent、單次指令收到temporary而父shell最後仍是parent。所有操作在本地無網路隔離容器完成,容器已退出,沒有改系統全域設定。本案例版本是Bash 5.2.21,參考Bash文件來自官方5.2.37發行包;補丁版本不同已分開記錄。

遇到「腳本讀不到設定」時,可以先用這組小測試驗證你的啟動方式,再回到實際腳本。保留同一個專屬名字、明確的unset與限定輸出,容易看出值在哪一層消失。確定程序真的收到值後,才查程式是否使用另一個名字、是否有自己的預設值,或是否在啟動後把它覆蓋。這樣不必靠反覆修改全域設定檔猜原因。

參考資料

原文鏈接:https://wntheme.com/linux-shell-export-child-environment/,轉載請註明出處。
0

評論0

顯示驗證碼
沒有帳號?註冊  忘記密碼?