為什麼大括號命令組在 POSIX Shell 語法中的左大括號後需要空格?
TL;DR:為什麼 POSIX 大括號組在
{
保留字後需要空格,而 subshell 在保留字後不需要空格(
?POSIX shell 語法定義大括號組和子shell 如下
brace_group : Lbrace compound_list Rbrace subshell : '(' compound_list ')'
現在,如果我們從字面上理解,空格很重要。這意味著必須有空格來描述左大括號和圓括號,如
{ echo hello world; } ( echo hello world )
這也將與復合命令定義保持一致:
這些複合命令中的每一個在開頭都有一個保留字或控制運算符,在末尾有一個相應的終止符保留字或運算符。
然而,沒有意義的是為什麼
(list)
並且( list )
工作得很好((
不需要後面的空格),但是大括號擴展必須有一個前導空格,即{echo hello;}
不起作用。當然,保留字被視為 shell 字之後需要一個空格以與欄位拆分的概念保持一致是有意義的,但是定義本身並沒有提及空格。此外,如果
{
和(
都被複合命令的 POSIX 定義視為保留字,為什麼在這些保留字之後的空格字元方面對它們進行不同的處理?現在,ksh(1)手冊確實說明了:單詞是字元序列,由不帶引號的空白字元(空格、製表符和換行符)或元字元(<、>、|、;、&、(和))分隔
換句話說,ksh 將辨識
(
為單詞分隔符是有意義的,其中第一個單詞將是命令或變數賦值。然而,POSIX 似乎沒有提到(
元字元。就 POSIX 語法而言,我發現的唯一可能的解釋是它{
被認為是一個“令牌”,其中 as(
沒有被列為一個。/* These are reserved words, not operator tokens, and are recognized when reserved words are recognized. */ %token Lbrace Rbrace Bang /* '{' '}' '!' */
那麼這種差異的確切原因是什麼?
接受的答案說明:
例如,’(’ 和 ‘)’ 是控制運算符,因此
<space>
(list) 中不需要。但是,’{’ 和 ‘}’ 是 {list;} 中的保留字,因此在這種情況下,前導<space>
和<semicolon>
是必需的。
- 接受 Kusalananda 的回答。Kusalananda 的回答解決了我的需要,儘管主要是從非正式和直覺的角度來看;它指出
{
是保留字並且(
是運算符。Michael Homer 在評論中也提到了同樣的內容——複合命令定義聲明(強調添加):這些複合命令中的每一個在開頭都有一個保留字或控制運算符
{
被定義為保留字,類似於for
orwhile
,列在 Shell Grammar 中(參見問題中的最後一個程式碼塊)- 第 2.9 節指出(已添加重點):
特別是,這些表示包括在不需要 s的某些地方的標記之間的間距
<blank>
(當其中一個標記是運算符時)。
- 雖然標準沒有明確定義
(
為運算符,但(
稱為運算符;具體來說,第 2.9.2 節說如果管道以保留字開頭!並且 command1 是子 shell 命令,應用程序應確保 command1 開頭的 ( 運算符與 ! 分隔一個或多個字元。保留字 ! 的行為緊跟 ( 運算符是未指定的。
- Digital Trauma關於 Stack Overflow的問題指出了關於保留字的第 2.4 節:
只有在沒有引用任何字元並且該詞被用作以下情況時,才會發生這種辨識:
- 命令的第一個字
- 正如 Kusalananda 的回答“POSIX 語法中顯示的空格不是 shell 輸入數據中需要存在的空格,而只是顯示語法本身的一種方式。事實上,大括號是保留字,這意味著它們必須被空格包圍”正如邁克爾荷馬在評論中提到的那樣:“如果空格本身很重要,則需要在生產中列出它們”
結案。
這是 shell 將行分成標記的方式的限制。
shell從輸入文件中讀取行,並根據第 2 節“Shell 介紹”將它們轉換為單詞或運算符:
- shell 將輸入分解為標記:單詞和運算符
{ 是保留字
保留字是對 shell 具有特殊意義的字。下列字應視為保留字:
! { } case do done elif else esac fi for if in then until while
詞,要被辨識為詞,必須是定界的。
保留字僅在分隔時才被辨識…
主要是通過空格(第 7 點)和操作員。
- 如果目前字元是未引用的 <blank>,則包含前一個字元的任何標記都將被分隔,並且目前字元應被丟棄。
( 是一個運算符
而運算符本身就是分隔符。
其中“操作員”是:
3.260 運算符
在 shell 命令語言中,可以是控制運算符或重定向運算符。
重定向運算符
在 shell 命令語言中,執行重定向功能的標記。它是以下符號之一:
< > >| << >> <& >& <<- <>
3.113 控制操作員
在 shell 命令語言中,執行控制功能的令牌。它是以下符號之一:
& && ( ) ; ;; newline | ||
結論
因此,’(’ 和 ‘)’ 是控制運算符,而 ‘{’ ‘}’ 是保留字。
您的問題的完全相同的描述在規範中:
例如,’(’ 和 ‘)’ 是控制運算符,因此 (list) 中不需要 <space>。但是,’{’ 和 ‘}’ 是 { list;} 中的保留字,因此在這種情況下需要前導 <space> 和 <semicolon>。
這正好解釋了為什麼在
{
.這是有效的:
{ echo yes;}
就像這樣:
{(echo yes);}
這:
{(echo yes)}
甚至這樣:
{>/dev/tty echo yes;}
花括號和圓括號的區別在於花括號 (和
!
) 是保留字,就像for
,等一樣if
,then
而圓括號是控制運算符。單詞需要用空格分隔。這意味著就像你不能擁有
foriin*; do
你不能擁有
{somecommand;} >file
或者
if !somecommand; then
POSIX 語法中顯示的空格不是 shell 輸入數據中需要存在的空格,而只是顯示語法本身的一種方式。事實上,大括號是保留字,這意味著它們必須被空格包圍,而子shell 的括號則不需要。