Linux

將命令的輸出讀入 Bash 中的變數更有效或推薦什麼方法?

  • June 14, 2021

如果要將系統命令的單行輸出讀入 Bash shell 變數,您至少有兩個選項,如下例所示:

  1. IFS=: read user x1 uid gid x2 home shell <<<$(grep :root: /etc/passwd | head -n1)

  1. IFS=: read user x1 uid gid x2 home shell < <(grep :root: /etc/passwd | head -n1)

這兩者有什麼區別嗎?什麼更有效或推薦?


請注意,讀取/etc/passwd文件只是為了舉例。我的問題的重點是這裡的字元串程序替換

首先請注意,使用readwithout-r是處理輸入 where\用於轉義欄位或行分隔符,而/etc/passwd. 您想在read沒有-r.

現在關於這兩種形式,請注意兩者都不是標準sh語法。<<<來自zsh1991 年。<(...)來自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 | cmd1cmd2的輸出連接到管道的寫入端。然後<(...)擴展到標識另一端的路徑,為< that-path您獲取另一端的文件描述符。因此,無需外殼對數據進行任何操作即可cmd2直接對話。cmd1

bash您會在shell中看到這種構造,具體是因為 in bash,與 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一樣是多餘的-rread反正只會讀一行²。我添加了一個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

¹ 除非設置了lastpipein 選項bash並且 shell 像在腳本中一樣是非互動式的

² 另見GNU 實現的-m1or選項,它會告訴自己在第一次匹配後停止搜尋。或攜帶式等效物:--max-count=1``grep``grep``sed '/:root:/!d;q'

引用自:https://unix.stackexchange.com/questions/654067