7

I am reading the bash source code, and the BNF grammar for bash would be:

<pipeline_command> ::= <pipeline>
                    |  '!' <pipeline>
                    |  <timespec> <pipeline>
                    |  <timespec> '!' <pipeline>
                    |  '!' <timespec> <pipeline>

<pipeline> ::=
          <pipeline> '|' <newline_list> <pipeline>
       |  <command>

Does this means ! command is a kind of pipe too.

! ls works, however it's the same as ls.

! time ls works too.

That's quite different to | pipe.

How to use ! in bash? Is it a pipe?

AdminBee
  • 21,637
  • 21
  • 47
  • 71
frams
  • 516
  • 5
  • 17

4 Answers4

8

From the bash manual: "If the reserved word ! precedes a pipeline, the exit status of that pipeline is the logical negation of the exit status"

You are misreading the grammar. What the grammar says is that you can put a ! in front of a pipeline, not replace | with a !.

Johan Myréen
  • 12,862
  • 1
  • 32
  • 33
5

The exclamation point just logically reverses the return code of the command/pipeline (see e.g. Bash's manual):

if true ;    then echo this prints ; fi
if ! false ; then echo this also prints ; fi
if ! true ;  then echo this does not print ; fi

The return code of a pipeline is (usually) just the return code of the last command, so the bang inverts that:

if ! true | false ; then echo again, this also prints ; fi
ilkkachu
  • 133,243
  • 15
  • 236
  • 397
2

Defining a pipeline to be one or more commands means a single command is also a pipeline, albeit one that doesn't actually involve a pipe. The benefit is that ! as a negation operator doesn't have to be defined separately for commands and pipelines; it need only be defined as applying to a pipeline.

In ! cmd1 | cmd2, the ! negates the exit status of the entire pipeline, not just the single command cmd1. The exit status of a pipeline, by default, is the exit status of the right-most command.


Likewise, a list is one more pipelines joined by ;, &, &&, or ||. Thus, a single pipeline is also a list, and a single command is also a list. Then, when a command like if is defined as taking a list between the if and then keywords, this automatically includes single commands and single pipelines as part of the definition of a command.

  • A list consisting of two pipelines (one of which only consists of one command):

    if IFS= read -r response && echo "$response" | grep foo; then
    
  • A list consisting of a single pipeline:

    if echo "$foo" | grep foo; then
    
  • A list consisting of single pipeline (which itself contains only a single command):

    if true; then
    
chepner
  • 7,341
  • 1
  • 26
  • 27
2

A couple of points to add to what the other answers said:

  • As noted (indirectly) by chepner’s answer, the ! operator is defined as an optional prefix to the <pipeline_command> syntactic element, rather than to <command>.  This has the consequence that you cannot say

      cmd1 | ! cmd2
    

    or

    ! cmd1 | ! cmd2
    

    You can only negate the exit status of an “entire pipeline”.  As chepner pointed out, a “pipeline” can be a single command, so you can do things like

    ! cmd1 && ! cmd2; ! cmd3 || ! cmd4
    

    but that’s silly.  The ! before cmd2 does nothing whatsoever; the ! before cmd4 affects only the value of $? at the end of the command, and the other two can be eliminated by exchanging the AND and the OR:

      cmd1  ||  cmd2;   cmd3  &&  cmd4
    

    Similarly, while ! cmd can be replaced with until cmd.

  • A <pipeline> preceded by a ! becomes a <pipeline_command> — a different syntactic element.  Therefore, it is not valid to say

    ! ! cmd1
    

    unlike arithmetic expansion, where things like $((! ! value)) and $((! ! ! value)) are valid.

  • Be advised that POSIX defines the same grammar, but uses different element names.  The BNF in the question appears in POSIX as

    pipeline         :      pipe_sequence
                     | Bang pipe_sequence
    
    pipe_sequence    :                             command
                     | pipe_sequence '|' linebreak command
    

    where Bang is a %token with the value '!'.