好吧,我知道在Bash 中(預設情況下,沒有啟用“lastpipe”bash 選項)管道之後分配的每個變數實際上都是在子shell 中執行的,並且變數本身在子shell 執行後就會消失。它不再可供父進程使用。但做了一些測試,我發現了這種行為:
A) 第二條指令 (a=2) 賦值並回傳:
[root@centos01]# a=1; a=2; a=10 | echo $a
2
B) 第三條指令(a=10)賦值並回傳:
[root@centos01]# a=1; a=2; a=10; a=20 | echo $a
10
C) 第四條命令(a=20)賦值並回傳:
[root@centos01]# a=1; a=2; a=10; a=20; touch fileA.txt | echo $a
20
所以:
為什麼命令序列中的最後一個變數賦值總是沒有實際執行? (或者如果是,為什麼它沒有被子 shell 捕獲並由 echo 命令返回?)
在測試 C 中,「touch」指令實際上在目錄中建立了檔案「fileA.txt」。那麼,為什麼步驟 A 和集合 B 中執行的變數賦值序列中的最後一個指令不起作用?有人知道這方面的技術解釋嗎?
答案1
首先,為了就幾個名稱達成一致,以下是 shell 解釋您的輸入的方式:
$ a=1; a=2; a=10 | echo $a
^^^ ^^^ ^^^^^^^^^^^^^^
\ \ \_ Pipeline
\ \_ Simple command
\_ Simple command
該管道由兩個簡單的命令組成:
$ a=10 | echo $a
^^^^ ^^^^^^^
\ \_ Simple command
\_ Simple command
(請注意,雖然 Bash 手冊中可能沒有明確說明,但POSIX shell 語法允許僅由變數賦值構成簡單命令)。
a=1;
並且a=2;
不屬於任何管道的一部分。 A;
將終止管道,除非作為 a 的一部分出現複合命令。例如:
{ a=1; a=2; a=10; } | echo $a
在您的範例中,a=10
和echo $a
執行於兩個不同的、 獨立的子 shell 環境1,皆以主環境的副本創建。子 shell 不得更改其父執行環境2。引用相關內容POSIX 部分:
子 shell 環境應建立為 shell 環境的副本 [...] 對子 shell 環境所做的變更不應影響 shell 環境。
和
此外,多命令管道中的每個命令都位於子shell環境中;然而,作為擴展,管道中的任何或所有命令都可以在當前環境中執行。所有其他命令應在目前 shell 環境中執行。
因此,雖然範例中的所有命令都實際執行,但管道左側部分的分配沒有明顯的效果:它們僅更改a
各自子 shell 環境中的副本,一旦子 shell 終止,這些副本就會遺失。
管道兩端的子 shell 可以直接相互互動的唯一方式是透過管道本身 - 左側的標準輸出連接到右側的標準輸入。由於a=10
不透過管道發送任何內容,因此它不可能影響echo $a
.
1 如果lastpipe
設定了該選項(預設關閉,可以使用shopt
內建指令啟用),Bash 可能會執行目前 shell 中管道的最後一個指令。看管道在 Bash 手冊中。但是,這與您的問題無關。
2 您可以從實踐/歷史的角度找到有關 U&L 的更多詳細信息,例如,這個答案到為什麼 POSIX shell 腳本管道中執行的最後一個函數不保留變數值?
答案2
請原諒我的英語,我還在學習。我也是Bash初學者,所以請糾正我的回答中的錯誤,謝謝。
首先我要指出一個錯誤。
當
a=10 | echo $a
您透過管道(使用管道運算子;|
)a=10
來回顯命令時。管道會將stdout
a 指令連接到stdin
command2,即command | command2
。a=10
是一個變數賦值,我假設沒有stdout
它,因為它不是一個命令。如果您在變數賦值中執行命令替換,則它將不起作用。如果我嘗試以下操作:user@host$ a=$(b=10); echo $a
不
echo $a
傳回值10
。當我修改為user@host$ a=$(b=10; echo $b)
的一個電話
$ echo $a
回
10
。所以,我可能正確地假設變數賦值不是一個命令(即使透過 bash 手動定義它也不是一個命令)。
其次,此echo
指令不從 取得輸入stdin
,而是列印其參數。
user@host$ echo "I love linux" | echo
不會返回任何內容。您可以使用xargs
命令來克服這個問題:
user@host$ echo "I love linux" | xargs echo
將返回I love linux
。因此,管道不能直接在echo
命令上工作,因為它打印其參數而不是stdin
.
現在,進行測試
在你的指揮下
user@host$ a=1; a=2; a=10 | echo $a
變數最初
a
被賦值1
,然後變數的值改為2
,兩者都在目前 shell 環境中。命令通常在子 shell 中運行。a=10 | echo $a
是一個列表,即相當於(a=10 | echo $a)
在子 shell 中運行的列表,但它不起作用,因為echo
does not takestdin
,但只列印它的參數。這裡,參數是,子 shell 中$a
變數的值是。a
2
此外,
a=10
不會產生任何輸出,因為它是變數賦值。因此,實際上echo $a
是列印其參數的值,2
而不是從 中獲取任何內容a=10 | < ... >
。所以,這裡不應該使用管道運算符;相反,您可以使用終止符(分號)分隔命令名稱和變數賦值,並且它會正常工作,如(a=10; echo $a)
.
為了更好地理解這一點,您可以在啟用 bash 偵錯選項的情況下嘗試以下範例:
user@host$ a=1; a=2; a=10; echo $a;
在上面的命令列中,
echo $a
生成10
.如果我把它改成
user@host$ a=1; a=2; (a=10; echo $a)
第一個和第二個變數賦值是在目前 shell 中設定的,第三個變數賦值和
echo
命令是在子 shell 中執行的。因此, 的值a
也在執行10
該指令的子 shell 中echo
,因此傳回10
。收到提示後,如果發出命令echo $a
,它將返回,2
因為子 shell 中的變數分配不會傳回父 shell。- 還要注意的一件事是,
;
命令分隔符號按順序執行命令。
在測試案例「A」和「B」中,實際上執行了最後一個變數賦值(a=10
在測試 A 和a=20
測試 B 中),但它是在命令之後執行的,echo $a
因此您得到的是變數先前值的結果兩個命令的和是在命令執行之前連接的,變數賦值也不會在標準輸出上產生任何內容。a
stdin
stdout
太長了;博士: 不應在管道中使用變數賦值。echo
不直接在管道中工作。
答案3
這裡,
a=20 | echo $a
管道只會增加混亂。左側的賦值不會將任何內容列印到標準輸出,也不echo
從標準輸入讀取任何內容,因此沒有資料通過管道移動。的參數echo
只是從先前設定的內容擴展而來。
正如您所說,管道的各個部分在不同的子外殼中運行,因此左側的分配a
不會影響右側的擴展,並且在相反的情況下也不會發生這種通訊。
相反,如果你這樣做:
{ a=999; echo a=$a; } | cat
管子會更有意義,繩子a=999
會穿過它。
最後一個範例中的程式碼touch fileA.txt
之所以有效,是因為它會影響 shell 外部的系統。同樣,您可以從管道中的命令寫入 stderr 並查看終端上顯示的結果:
$ echo a >&2 | echo b >&2 | echo c >&2
b
c
a
(這確實是我在系統上使用 Bash 得到的輸出順序。)