Bash

了解 Bash 的讀取文件命令替換

  • September 29, 2021

我試圖了解 Bash 是如何處理以下行的:

$(< "$FILE")

根據 Bash 手冊頁,這相當於:

$(cat "$FILE")

我可以按照第二行的推理。Bash 對 執行變數擴展$FILE,輸入命令替換,傳遞$FILEto的值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中,除了只有一個文件輸入重定向(<file0< file,否<&3,,<<<here< a < b)時才會觸發特殊行為

但是,除了在模擬其他 shell 時,在:

< file
<&3
<<< here...

也就是說,當只有輸入重定向而沒有命令時,在命令替換之外,zsh執行$READNULLCMD(預設情況下是尋呼機),並且當同時存在輸入和輸出重定向時,$NULLCMDcat預設情況下),所以即使$(<&3)不被辨識為特殊操作員,它仍然可以ksh通過呼叫尋呼機來執行它(該尋呼機的行為就像cat因為它的標準輸出將是一個管道)。

但是,雖然ksh’s$(< a < b)會擴展為a, in的內容zsh,但它會擴展為aand的內容b(或者僅b當該multios選項被禁用時),$(< a > b)會復製ab並擴展為空,等等。

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將是一個 builtin cat
  • 由於上述原因,在其中所做的任何更改都會在之後失去($(<${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
)

進行了優化,因為此處文件的內容(沒有尾隨換行符)在沒有使用臨時文件或管道的情況下進行了擴展,這與此處文件的情況不同,這使其成為有效的多行引用語法。

要移植到 , 和 的所有版本kshzsh最好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,並被添加到bashfrom Bash-2.02

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