Почему восклицательный знак в двойных кавычках вызывает ошибку Bash?

25

Пожалуйста, посмотрите на эти команды:

$ notify-send SYNC TIME!
$ notify-send 'SYNC TIME!'
$ notify-send "SYNC TIME!"
bash: !": event not found
$

Первые две команды выдают сообщение, как и ожидалось. Третий дает показанную ошибку.

а также

$ echo SYNC TIME!
SYNC TIME!
$ echo 'SYNC TIME!'
SYNC TIME!
$ echo "SYNC TIME!"
bash: !": event not found
$

Здесь также echoработает для первых двух команд, но не для третьей.

Больше проблем здесь (хотя я не планировал использовать это): как notify-send "SYNC!TIME"и echo "SYNC!TIME"отдавание bash: !TIME": event not found.

Но оба notify-sendи echoработают с"SYNC! TIME"

Может кто-нибудь объяснить, почему bash: !": event not foundпоявляется ошибка?

Правосудие для Моники
источник

Ответы:

31

!является символом раскрытия истории по умолчанию в Bash, см. раздел «РАСШИРЕНИЕ ИСТОРИИ» на справочной странице Bash

  • Расширение истории не происходит, если !оно заключено в одинарные кавычки, как в

    notify-send 'SYNC TIME!'
  • Расширение истории не происходит, если за !ним следует пробел, табуляция, перевод строки, возврат каретки или =, как в

    notify-send SYNC TIME!
  • Раскрывание делает иметь место в

    echo "SYNC TIME!"

    Таким образом, вы получите сообщение об ошибке, если "в вашей истории нет команды, начинающейся с

Флориан Диш
источник
4
Это неправильное поведение можно исправить, добавив в свою .bashrcстроку set +H. Обратите внимание, что !это уже не является особенным в сценариях; обращение с ним как с особым нарушило бы многие сценарии, соответствующие стандартам. В интерактивных оболочках он рассматривается только как «особый» и только по умолчанию, пока вы его не исправите. :-)
R ..
15

Поскольку в bash !это зарезервированное слово (ОК, символ), оно имеет особое значение в разных контекстах. В этом конкретном случае вы попадаете в тупик его значения в поиске истории. От man bash:

   History expansions introduce words from the history list into the input
   stream, making it easy to repeat commands, insert the  arguments  to  a
   previous command into the current input line, or fix errors in previous
   commands quickly.

  [...]

   History expansions are introduced by
   the appearance of the  history  expansion  character,  which  is  !  by
   default.   Only  backslash  (\) and single quotes can quote the history
   expansion character.

По сути, это означает, что bash будет принимать символы после !и искать в вашей истории первую найденную команду, которая начинается с этих символов. Это проще продемонстрировать, чем объяснить:

$ echo foo
foo
$ !e
echo foo
foo

!Активируется расширение истории, которая соответствует первой команде , начиная с eкоторого было ранее запустить echo fooкоторый затем запустить снова. Итак, когда вы написали "SYNC TIME!", bash увидел !", просмотрел историю для команды, начинающейся с ", потерпел неудачу и пожаловался на это. Вы можете получить ту же ошибку, запустив, например !nocommandstartswiththis.

Чтобы напечатать восклицательный знак, вам нужно убрать его одним из следующих двух способов:

echo 'Hello world!'
echo Hello world\!
terdon
источник
6
echo Hello world!работает --- расширение истории не запускается пробелами ;-) (ах, хорошие угловые случаи ...)
Rmano
1
Хотя лучше избегать восклицательного знака, чем пробелы.
Этеш Чоудхури
1
@Rmano Я предпочитаю прямо говорить, а не наставлять меня в поведении, которое можно изменить
Брайам
!это зарезервированное слово в bash, так как POSIX также заявляет . Но я уверен, что его статус как такового совершенно не связан с его ролью в расширении истории и не имеет отношения к его обращению в двойных кавычках. !является зарезервированным словом, потому что оно отрицает статус завершения команды / конвейера, поэтому его нельзя использовать в качестве команды. Ни одно другое зарезервированное слово не является особенным в "-quoted контексте, в то время как $это не является зарезервированным словом , но это лечение специально.
Элия ​​Каган