Использование Bang (!) в bash

Использование Bang (!) в bash

Я читаю исходный код 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

Восклицательный знак просто логически меняет код возврата команды/конвейера (см., например,Руководство Баша):

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

Так уж получилось, что я не вижу этого файла BNF в исходном дистрибутиве Bash, а процитированная грамматика не совсем точна, поскольку Bash принимает множественные восклицательные знаки, начиная с версии Bash 4.2 (выпущенной в 2011 году):

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со значением '!'.

Связанный контент