Dlaczego wykrzyknik w podwójnych cudzysłowach powoduje błąd Bash?

25

Proszę spojrzeć na te polecenia:

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

Pierwsze dwa polecenia generują dymek powiadomienia zgodnie z oczekiwaniami. Trzeci podaje pokazany błąd.

i

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

Tutaj również echodziała dla pierwszych dwóch poleceń, ale nie dla trzeciego.

Więcej problemów tutaj (chociaż nie planowałem tego użyć): zarówno notify-send "SYNC!TIME"i echo "SYNC!TIME"daj bash: !TIME": event not found.

Ale oba notify-sendi echopracuj z"SYNC! TIME"

Czy ktoś może wyjaśnić, dlaczego bash: !": event not foundpojawia się błąd?

Sprawiedliwość dla Moniki
źródło

Odpowiedzi:

31

!jest domyślnym rozszerzeniem historii w Bash, patrz sekcja „ROZSZERZENIE HISTORII” na stronie podręcznika Bash

  • Ekspansja historii nie ma miejsca, jeśli !jest ujęta w pojedyncze cytaty, jak w

    notify-send 'SYNC TIME!'
  • Ekspansja historii nie ma miejsca, jeśli po niej !następuje spacja, tabulator, nowa linia, powrót karetki lub =, jak w

    notify-send SYNC TIME!
  • Ekspansja historia ma odbywać się w

    echo "SYNC TIME!"

    Otrzymasz błąd, jeśli "w historii nie ma komendy

Florian Diesch
źródło
4
To niewłaściwe zachowanie można naprawić, dodając do .bashrclinii set +H. Zauważ, że nie !jest już wyjątkowy w skryptowaniu; traktowanie go jako specjalnego złamałoby wiele skryptów zgodnych ze standardami. Jest on traktowany jako „specjalny” w interaktywnych powłokach i tylko domyślnie, dopóki go nie naprawisz. :-)
R ..
15

Ponieważ w bash !jest słowem zastrzeżonym (OK, znak), ma specjalne znaczenie w różnych kontekstach. W tym konkretnym przypadku nie ma znaczenia, jakie ma znaczenie w wyszukiwaniu historii. Od 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.

Zasadniczo oznacza to, że bash weźmie znaki po !i przeszuka Twoją historię w poszukiwaniu pierwszego znalezionego polecenia, które zaczyna się od tych znaków. Łatwiej jest zademonstrować niż wyjaśnić:

$ echo foo
foo
$ !e
echo foo
foo

!Aktywowane rozszerzenie historii, co zgadzało się z pierwszego polecenia wychodząc z ektórej uprzednio prowadzony echo fooktóry następnie uruchomić ponownie. Więc kiedy pisałeś "SYNC TIME!", bash zobaczył !", przeszukał historię polecenia zaczynającego się od ", zawiódł i narzekał na to. Ten sam błąd można uzyskać na przykład przez uruchomienie !nocommandstartswiththis.

Aby wydrukować wykrzyknik, musisz uciec przed nim na jeden z dwóch sposobów:

echo 'Hello world!'
echo Hello world\!
terdon
źródło
6
echo Hello world!działa --- ekspansja historii nie jest wywoływana przez spacje ;-) (ah, fajne
kąciki
1
Lepiej jednak polegać na ucieczce od wykrzyknika niż na pustych miejscach.
Ehtesh Choudhury,
1
@Rmano Wolę to powiedzieć wprost, niż kierować się zachowaniem, które można zmienić
Braiam
!jest słowem zastrzeżonym w bash, jak deklaruje również POSIX . Ale jestem prawie pewien, że jego status jako jednego jest całkowicie niezwiązany z jego rolą w ekspansji historii i nie ma znaczenia dla jego traktowania w podwójnych cytatach. !jest słowem zastrzeżonym, ponieważ neguje status wyjścia polecenia / potoku, więc nie można go użyć jako polecenia. Żadne inne słowo zarezerwowane jest wyjątkowy w "-quoted kontekście, choć $to nie słowem zarezerwowanym ale jest traktowany specjalnie.
Eliah Kagan