Я читаю исходный код 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
но это глупо.
!
Beforecmd2
вообще ничего не делает;!
beforecmd4
влияет только на значение$?
в конце команды, а два других можно исключить, поменяв местами 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
со значением'!'
.