括號真的將指令放入子 shell 中嗎?

括號真的將指令放入子 shell 中嗎?

根據我的閱讀,將命令放在括號中應該在子 shell 中運行它,類似於運行腳本。如果這是真的,那麼如果 x 沒有導出,它如何看到變數 x 呢?

x=1

(echo $x)在命令列上運行結果為 1

echo $x正如預期的那樣,在腳本中運行不會產生任何結果

答案1

子 shell 最初是與原始 shell 進程幾乎相同的副本。在底層,shell 調用fork系統呼叫1,建立一個新進程,其程式碼和記憶體是副本2。創建子 shell 後,它與其父 shell 之間幾乎沒有什麼區別。特別是,它們具有相同的變數。即使$$特殊變數在子 shell 中也保持相同的值:它是原始 shell 的進程 ID。同樣,$PPID原始 shell 的父級 PID 也是如此。

有些 shell 會更改子 shell 中的一些變數。 Bash ≥4.0 設定BASHPID為 shell 進程的 PID,該 PID 在子 shell 中變更。 Bash、zsh 和 mksh 安排$RANDOM在父 shell 和子 shell 中產生不同的值。但除了像這樣的內建特殊情況外,所有變數在子 shell 中都具有與原始 shell 中相同的值、相同的導出狀態、相同的唯讀狀態等。也會被繼承。

建立的子 shell(…)與其建立者俱有相同的檔案描述符。創建子 shell 的其他一些方法會在執行使用者程式碼之前修改一些檔案描述符;例如,管道的左側在子 shell 3中運行,標準輸出連接到管道。子 shell 也以相同的當前目錄、相同的訊號掩碼等開始。trap '' SIGNALtrap CODE訊號) 重設為預設操作4 .

因此,子 shell 與執行腳本不同。腳本是一個單獨的程式。這個單獨的程式可能碰巧也是由與父程式相同的解釋器執行的腳本,但是這種巧合並沒有使單獨的程式對父程式的內部資料有任何特殊的可見性。非導出變數是內部數據,因此當子 shell 腳本的解釋器是被處決,它看不到這些變數。導出的變量,即環境變量,被傳輸到執行的程序。

因此:

x=1
(echo $x)

列印,1因為子 shell 是產生它的 shell 的複製品。

x=1
sh -c 'echo $x'

碰巧將 shell 作為 shell 的子進程運行,但是第二行上的 與第二行上的x沒有比x

x=1
perl -le 'print $x'

或者

x=1
python -c 'print x'

1 除非 shell 最佳化分叉,但會根據需要模擬分叉以保留其正在執行的程式碼的行為。 Ksh93 進行了許多最佳化,而其他 shell 大多沒有。
2 從語義上講,它們是副本。從實施的角度來看,有很多共享正在進行中。
3 對於右側,取決於外殼。
4 如果您對此進行測試,請注意$(trap)可能會報告原始外殼的陷阱。另請注意,許多 shell 在涉及陷阱的極端情況下都存在錯誤。例如忍者請注意,從 bash 4.3 開始,在「兩個子 shell」情況下從嵌套子 shellbash -x -c 'trap "echo ERR at \$BASH_SUBSHELL \$BASHPID" ERR; set -E; false; echo one subshell; (false); echo two subshells; ( (false) )'運行陷阱,而不是從中間子 shell 運行陷阱 -選項應該將陷阱傳播到所有子shell,但中間子shell 已被優化掉,所以不是不要在那裡運行它的陷阱。ERRERRset -EERRERR

答案2

顯然,是的,正如所有文件所說,帶有括號的命令是在子 shell 中運行的。

子 shell 繼承所有父級變數的副本。不同之處在於,您在子 shell 中所做的任何變更都不會在父 shell 中進行。

ksh 手冊頁使這一點比 bash 更清晰:

man ksh:

括號的命令在子 shell 中執行,而不刪除非匯出變數。

man bash:

(清單)

list 在子 shell 環境中執行(請參閱下方的指令執行環境)。影響 shell 環境的變數分配和內建指令在指令完成後不再有效。

命令執行環境

shell 有一個執行環境,由以下部分組成: [...] 透過變數賦值設定的 shell 參數 [...]。
命令替換、用括號分組的命令以及異步命令在與 shell 環境重複的子 shell 環境中調用,[...]

相關內容