10

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?

1
  • 1
    The output of ! ls and ls are the same; the exit status are different. Commented Nov 26, 2017 at 15:30

4 Answers 4

11

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 !.

0
8

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

As it happens, I can't see that BNF file in the Bash source distribution, and the quoted grammar isn't exactly accurate, as Bash does accept multiple bangs since Bash 4.2 (released in 2011):

if ! ! true ; then echo 'this prints too' ; fi

That's not standard, though, and e.g. zsh and Dash croak at it.

0
3

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
    
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 '!'.

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.