我正在閱讀 bash 原始碼,並且BNF語法對 bash 來說是:
<pipeline_command> ::= <pipeline>
| '!' <pipeline>
| <timespec> <pipeline>
| <timespec> '!' <pipeline>
| '!' <timespec> <pipeline>
<pipeline> ::=
<pipeline> '|' <newline_list> <pipeline>
| <command>
這是否意味著!
命令也是一種管道。
! ls
有效,但是它與 相同ls
。
! time ls
也有效。
這與|
管道完全不同。
如何!
在bash中使用?是管子嗎?
答案1
來自 bash 手冊:“如果保留字 ! 在管道之前,則該管道的退出狀態是退出狀態的邏輯非”
你誤讀了語法。文法說的是可以放一個 !前面有一個管道,不是替換|與!
答案2
感嘆號只是邏輯上反轉命令/管道的回傳代碼(參見例如Bash 的手冊):
if true ; then echo 'this prints' ; fi
if ! false ; then echo 'this also prints' ; fi
if ! true ; then echo 'this does not print' ; fi
管道的回傳代碼(通常)只是最後一個指令的回傳代碼,因此爆炸會反轉:
if ! true | false ; then echo 'again, this also prints' ; fi
碰巧的是,我在 Bash 原始碼發行版中看不到該 BNF 文件,並且引用的語法並不完全準確,因為自 Bash 4.2(2011 年發布)以來,Bash 確實接受多個感嘆號:
if ! ! true ; then echo 'this prints too' ; fi
但這不是標準的,例如 zsh 和 Dash 就在這方面表現不佳。
答案3
將管道定義為一或多個命令意味著單一命令也是管道,儘管實際上並不涉及管道。好處是,!
作為否定運算符,不必為命令和管道單獨定義;它只需定義為應用於管道。
在 中! cmd1 | cmd2
,!
取消整個管道的退出狀態,而不僅僅是單一命令cmd1
。預設情況下,管道的退出狀態是最右側指令的退出狀態。
同樣,一個清單是另一根由;
、&
、&&
、 或相連的管道||
。因此,單一管道也是一個列表,單一命令也是一個列表。然後,當像這樣的命令if
定義為採用if
和then
關鍵字之間的清單時,這會自動包括單一命令和單一管道作為命令定義的一部分。
由兩個管道組成的清單(其中一個僅包含一個命令):
if IFS= read -r response && echo "$response" | grep foo; then
由單一管道組成的清單:
if echo "$foo" | grep foo; then
由單一管道組成的清單(其本身僅包含單一命令):
if true; then
答案4
在其他答案的基礎上補充幾點:
正如(間接)指出的切普納的回答,
!
運算子被定義為語法元素的可選前綴<pipeline_command>
,而不是<command>
。這會帶來你不能說的後果cmd1 | ! cmd2
或者
! cmd1 | ! cmd2
您只能否定「整個管道」的退出狀態。正如切普納指出的那樣,“管道”可以是單一命令,因此您可以執行以下操作
! cmd1 && ! cmd2; ! cmd3 || ! cmd4
但這很愚蠢。 before
!
沒有cmd2
做任何事; before!
只影響 命令末尾cmd4
的值,其他兩個可以透過交換 AND 和 OR 來消除:$?
cmd1 || cmd2; cmd3 && cmd4
同樣,
while ! cmd
可以替換為until cmd
.A
<pipeline>
前面有 a!
就變成 a-<pipeline_command>
不同的句法元素了。因此,說! ! cmd1
與算術擴展不同,算術擴展中像 和 之類的東西都是有效的。
$((! ! value))
$((! ! ! value))
請注意POSIX定義相同的語法,但使用不同的元素名稱。問題中的 BNF 在 POSIX 中顯示為
pipeline : pipe_sequence | Bang pipe_sequence pipe_sequence : command | pipe_sequence '|' linebreak command
其中
Bang
a 的%token
值為'!'
。