將命令的輸出讀入 Bash 中的變數更有效或推薦什麼方法?
如果要將系統命令的單行輸出讀入 Bash shell 變數,您至少有兩個選項,如下例所示:
IFS=: read user x1 uid gid x2 home shell <<<$(grep :root: /etc/passwd | head -n1)
和
IFS=: read user x1 uid gid x2 home shell < <(grep :root: /etc/passwd | head -n1)
這兩者有什麼區別嗎?什麼更有效或推薦?
請注意,讀取
/etc/passwd
文件只是為了舉例。我的問題的重點是這裡的字元串與程序替換。
首先請注意,使用
read
without-r
是處理輸入 where\
用於轉義欄位或行分隔符,而/etc/passwd
. 您想在read
沒有-r
.現在關於這兩種形式,請注意兩者都不是標準
sh
語法。<<<
來自zsh
1991 年。<(...)
來自ksh
大約 1985 年,儘管ksh
最初不支持從/到它的重定向。
$(...)
也來自 ksh,但已被 POSIX 標準化(因為它取代了...
Bourne shell 中設計不良的),因此現在可以跨sh
實現移植。
$(code)
解釋子shell中的程式碼,同時將輸出重定向到管道,而父級同時從管道的另一端讀取該輸出並將其儲存在記憶體中。然後,一旦該命令完成,該輸出,去除尾隨換行符(並在 中刪除 NUL 字元bash
)構成$(...)
.如果它
$(...)
沒有被引用並且在列表上下文中,它會受到 split+glob 的影響(僅在 zsh 中拆分)。之後<<<
,它不是列表上下文,但舊版本的bash
仍然會進行拆分部分(不是 glob),然後用空格連接部分。因此,如果使用bash
,您可能還希望$(...)
在用作<<<
.
cmd <<< word
在 zsh 和舊版本的 bash 中,shell 將word
後跟換行符儲存到臨時文件中,然後將其作為將要執行的程序的標準輸入,並執行cmd
之前刪除的臨時文件cmd
。<< EOF
這與70 年代 Bourne shell 的情況相同。實際上,它與以下內容完全相同:cmd << EOF word EOF
在 5.1 中,bash 從使用臨時文件切換到使用管道,只要單詞可以在管道緩衝區中完全容納(如果不避免死鎖,則回退到使用臨時文件)並使
cmd
’s stdin 的讀取結束外殼預先用word
.因此
cmd1 <<< "$(cmd2)"
涉及一個或兩個管道,將整個輸出cmd2
儲存在記憶體中,將其再次儲存在另一個管道或臨時文件中,並破壞 NUL 和換行符。
cmd1 < <(cmd2)
是等效於 的功能cmd2 | cmd1
。cmd2
的輸出連接到管道的寫入端。然後<(...)
擴展到標識另一端的路徑,為< that-path
您獲取另一端的文件描述符。因此,無需外殼對數據進行任何操作即可cmd2
直接對話。cmd1
bash
您會在shell中看到這種構造,具體是因為 inbash
,與 AT&T ksh 或 zsh 相反,在:cmd2 | cmd1
cmd1
在子外殼中執行¹,因此例如,如果cmd1
是,則只會填充該子外殼的變數。read``read
所以在這裡,你會想要:
IFS=: read -r user x1 uid gid x2 home shell rest_if_any_ignored < <( grep :root: /etc/passwd)
和
head
一樣是多餘的-r
,read
反正只會讀一行²。我添加了一個rest_if_any_ignored
用於將來的校對,以防將來添加一個新欄位/etc/passwd
,從而導致$shell
包含/bin/sh:that-field
其他內容。可移植(在 中
sh
),您不能這樣做:grep :root: /etc/passwd | IFS=: read -r user x1 uid gid x2 home shell rest_if_any_ignored
因為 POSIX 未指定是否
read
在子外殼中執行(如bash
/dash
…)或不執行(如zsh
/ksh
)。但是,您可以這樣做:
IFS=: read -r user x1 uid gid x2 home shell rest_if_any_ignored << EOF $(grep :root: /etc/passwd | head -n1) EOF
(這裡恢復
head
以避免將整個grep
輸出儲存在記憶體和臨時文件/管道中)。即使效率不高,這也是標準的(儘管正如@muru 所指出的,與在分叉程序中執行外部實用程序的成本相比,如此小的輸入的差異可能可以忽略不計)。
性能,如果在這裡很重要,可以通過使用 shell 的內置功能來完成
grep
’s 工作。但是,特別是在 中bash
,您只能對非常小的輸入執行此操作,因為 shell 不是為此類任務設計的,並且會比grep
.while IFS=: read <&3 -r user x1 uid gid name home shell rest_if_any_ignored do if [ "$name" = root ]; then do-something-with "$user" "$home"... break fi done 3< /etc/passwd
¹ 除非設置了
lastpipe
in 選項bash
並且 shell 像在腳本中一樣是非互動式的² 另見GNU 實現的
-m1
or選項,它會告訴自己在第一次匹配後停止搜尋。或攜帶式等效物:--max-count=1``grep``grep``sed '/:root:/!d;q'