了解 Bash 的讀取文件命令替換
我試圖了解 Bash 是如何處理以下行的:
$(< "$FILE")
根據 Bash 手冊頁,這相當於:
$(cat "$FILE")
我可以按照第二行的推理。Bash 對 執行變數擴展
$FILE
,輸入命令替換,傳遞$FILE
to的值cat
,cat 將 的內容輸出$FILE
到標準輸出,命令替換通過用內部命令產生的標準輸出替換整行來完成,並且 Bash 嘗試像執行它一樣一個簡單的命令。但是,對於我上面提到的第一行,我將其理解為:Bash 在 上執行變數替換
$FILE
,Bash 打開$FILE
以讀取標準輸入,以某種方式將標準輸入複製到標準輸出,命令替換完成,並且 Bash 嘗試執行生成的標準輸出。有人可以向我解釋一下內容是
$FILE
如何從標準輸入到標準輸出的嗎?
$(<file)
(也適用於) 是由and<file
複製的 Korn shell 的特殊運算符。它看起來很像命令替換,但實際上並非如此。zsh``bash
在 POSIX shell 中,一個簡單的命令是:
< file var1=value1 > file2 cmd 2> file3 args 3> file4
所有部分都是可選的,您可以有僅重定向、僅命令、僅分配或組合。
如果有重定向但沒有命令,則執行重定向(因此 a
> file
將打開並截斷file
),但什麼也沒有發生。所以< file
打開
file
閱讀,但由於沒有命令,所以什麼也沒有發生。所以file
然後關閉,就是這樣。如果$(< file)
是一個簡單的命令替換,那麼它將擴展為空。在POSIX 規範中
$(script)
,如果script
僅包含重定向,則會產生未指定的結果。這是為了允許 Korn shell 的特殊行為。
ksh93u+
在ksh中(這裡用0) 僅輸入 (<
,<<
或<<<
) 重定向,因此:
$(< file)
$(0< file)
$(<&3)
($(0>&3)
實際上也是同一個運算符)$(< file > foo 2> $(whatever))
但不是:
$(> foo < file)
- 也不
$(0<> file)
- 也不
$(< file; sleep 1)
- 也不
$(< file; < file2)
然後
- 除了第一個重定向之外的所有重定向都被忽略(它們被解析掉)
- 並且它擴展為文件/heredoc/herestring 的內容(或者任何可以從文件描述符中讀取的內容,如果使用類似
<&3
的內容)減去尾隨換行符。好像使用
$(cat < file)
除了那個
- 讀取是由外殼內部完成的,而不是由
cat
- 不涉及管道或額外過程
- 由於上述原因,由於里面的程式碼沒有在子shell中執行,因此之後的任何修改都會保留(如
$(<${file=foo.txt})
or$(<file$((++n)))
)- 讀取錯誤(儘管在打開文件或複製文件描述符時不會出現錯誤)會被靜默忽略。
在
zsh
中,除了只有一個文件輸入重定向(<file
或0< file
,否<&3
,,<<<here
…< a < b
)時才會觸發特殊行為但是,除了在模擬其他 shell 時,在:
< file <&3 <<< here...
也就是說,當只有輸入重定向而沒有命令時,在命令替換之外,
zsh
執行$READNULLCMD
(預設情況下是尋呼機),並且當同時存在輸入和輸出重定向時,$NULLCMD
(cat
預設情況下),所以即使$(<&3)
不被辨識為特殊操作員,它仍然可以ksh
通過呼叫尋呼機來執行它(該尋呼機的行為就像cat
因為它的標準輸出將是一個管道)。但是,雖然
ksh
’s$(< a < b)
會擴展為a
, in的內容zsh
,但它會擴展為a
and的內容b
(或者僅b
當該multios
選項被禁用時),$(< a > b)
會復製a
到b
並擴展為空,等等。
bash
有一個類似的運算符,但有一些區別:
- 之前允許評論,但之後不允許評論:
echo "$( # getting the content of file < file)"
有效,但:
echo "$(< file # getting the content of file )"
擴展為無。
- 像 in 一樣
zsh
,只有一個文件 stdin 重定向,雖然沒有回退到 a$READNULLCMD
,所以$(<&3)
,$(< a < b)
執行重定向但擴展為空。- 出於某種原因,雖然
bash
不呼叫cat
,但它仍然派生了一個通過管道提供文件內容的程序,這使得它比其他 shell 中的優化少得多。它實際上就像一個$(cat < file)
wherecat
將是一個 builtincat
。- 由於上述原因,在其中所做的任何更改都會在之後失去(
$(<${file=foo.txt})
例如,在上面提到的 中,該$file
分配隨後會失去)。In
bash
,IFS= read -rd '' var < file
(也適用於zsh
)是將文本文件的內容讀入變數的更有效方法。它還具有保留尾隨換行符的好處。另請參閱(在$mapfile[file]
模組中且僅適用於正常文件),它也適用於二進製文件。zsh``zsh/mapfile
請注意,
ksh
與 ksh93 相比,基於 pdksh 的變體有一些變化。有趣的是,在mksh
(那些 pdksh 派生的外殼之一)中,在var=$(<<'EOF' That's multi-line test with *all* sorts of "special" characters EOF )
進行了優化,因為此處文件的內容(沒有尾隨換行符)在沒有使用臨時文件或管道的情況下進行了擴展,這與此處文件的情況不同,這使其成為有效的多行引用語法。
要移植到 , 和 的所有版本
ksh
,zsh
最好bash
僅限於$(<file)
避免註釋,並牢記對內部變數所做的修改可能會或可能不會保留。
因為
bash
它是在內部為您完成的,所以擴展了文件名並將文件轉換為標準輸出,就像您要做$(cat < filename)
. 這是一個 bash 功能,也許您需要查看bash
原始碼才能確切了解它是如何工作的。這裡是處理此功能的函式(來自
bash
原始碼,文件builtins/evalstring.c
):/* Handle a $( < file ) command substitution. This expands the filename, returning errors as appropriate, then just cats the file to the standard output. */ static int cat_file (r) REDIRECT *r; { char *fn; int fd, rval; if (r->instruction != r_input_direction) return -1; /* Get the filename. */ if (posixly_correct && !interactive_shell) disallow_filename_globbing++; fn = redirection_expand (r->redirectee.filename); if (posixly_correct && !interactive_shell) disallow_filename_globbing--; if (fn == 0) { redirection_error (r, AMBIGUOUS_REDIRECT); return -1; } fd = open(fn, O_RDONLY); if (fd < 0) { file_error (fn); free (fn); return -1; } rval = zcatfd (fd, 1, fn); free (fn); close (fd); return (rval); }
$(<filename)
不完全等同於$(cat filename)
; 如果文件名以破折號開頭,後者將失敗-
。
$(<filename)
最初來自ksh
,並被添加到bash
fromBash-2.02
。