Estou lendo o código-fonte do bash e oGramática BNFpara bash seria:
<pipeline_command> ::= <pipeline>
| '!' <pipeline>
| <timespec> <pipeline>
| <timespec> '!' <pipeline>
| '!' <timespec> <pipeline>
<pipeline> ::=
<pipeline> '|' <newline_list> <pipeline>
| <command>
Isso significa que !
o comando também é uma espécie de tubo.
! ls
funciona, porém é o mesmo que ls
.
! time ls
funciona também.
Isso é bem diferente do |
tubo.
Como usar !
no bash? É um cano?
Responder1
Do manual do bash: "Se a palavra reservada! Precede um pipeline, o status de saída desse pipeline é a negação lógica do status de saída"
Você está interpretando mal a gramática. O que a gramática diz é que você pode colocar um ! na frente de um gasoduto, não substitua | com um !.
Responder2
O ponto de exclamação apenas inverte logicamente o código de retorno do comando/pipeline (veja, por exemplo,Manual do 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
O código de retorno de um pipeline é (geralmente) apenas o código de retorno do último comando, então o bang inverte isso:
if ! true | false ; then echo 'again, this also prints' ; fi
Acontece que não consigo ver o arquivo BNF na distribuição de origem do Bash, e a gramática citada não é exatamente precisa, já que o Bash aceita vários bangs desde o Bash 4.2 (lançado em 2011):
if ! ! true ; then echo 'this prints too' ; fi
Isso não é padrão, e por exemplo, zsh e Dash fazem isso.
Responder3
Definindo um pipeline a serumou mais comandos significa que um único comando também é um pipeline, embora não envolva realmente um pipeline. A vantagem é que, !
como operador de negação, não precisa ser definido separadamente para comandos e pipelines; ele só precisa ser definido como aplicável a um pipeline.
Em ! cmd1 | cmd2
, !
nega o status de saída de todo o pipeline, não apenas do único comando cmd1
. O status de saída de um pipeline, por padrão, é o status de saída do comando mais à direita.
Da mesma forma, umlistaé mais um pipeline unido por ;
, &
, &&
ou ||
. Assim, um único pipeline também é uma lista e um único comando também é uma lista. Então, quando um comando como if
é definido como uma lista entre as palavras-chave if
e then
, isso inclui automaticamente comandos únicos e pipelines únicos como parte da definição de um comando.
Uma lista que consiste em dois pipelines (um dos quais consiste apenas em um comando):
if IFS= read -r response && echo "$response" | grep foo; then
Uma lista que consiste em um único pipeline:
if echo "$foo" | grep foo; then
Uma lista que consiste em um único pipeline (que contém apenas um único comando):
if true; then
Responder4
Alguns pontos a acrescentar ao que as outras respostas disseram:
Como observado (indiretamente) porresposta de chepner, o
!
operador é definido como um prefixo opcional para o<pipeline_command>
elemento sintático, em vez de<command>
. Isto tem a consequência de que você não pode dizercmd1 | ! cmd2
ou
! cmd1 | ! cmd2
Você só pode negar o status de saída de um “pipeline inteiro”. Como apontou Chepner, um “pipeline” pode ser um único comando, então você pode fazer coisas como
! cmd1 && ! cmd2; ! cmd3 || ! cmd4
mas isso é bobagem. O
!
antescmd2
não faz absolutamente nada; o!
antescmd4
afeta apenas o valor de$?
no final do comando, e os outros dois podem ser eliminados trocando o AND e o OR:cmd1 || cmd2; cmd3 && cmd4
Da mesma forma,
while ! cmd
pode ser substituído poruntil cmd
.A
<pipeline>
precedido por a!
torna-se a<pipeline_command>
— um elemento sintático diferente. Portanto, não é válido dizer! ! cmd1
ao contrário da expansão aritmética, onde coisas como e são válidas.
$((! ! value))
$((! ! ! value))
Esteja avisado quePOSIXdefine a mesma gramática, mas usa nomes de elementos diferentes. O BNF na questão aparece no POSIX como
pipeline : pipe_sequence | Bang pipe_sequence pipe_sequence : command | pipe_sequence '|' linebreak command
onde
Bang
está a%token
com o valor'!'
.