Pourquoi le point d'exclamation entre guillemets doubles provoque-t-il une erreur Bash?

25

Veuillez regarder ces commandes:

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

Les deux premières commandes produisent une bulle de notification comme prévu. Le troisième donne l'erreur indiquée.

et

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

Ici aussi, les echotravaux pour les deux premières commandes mais pas dans la troisième.

Plus de problèmes ici (même si je n'avais pas l'intention de l'utiliser): les deux notify-send "SYNC!TIME"et echo "SYNC!TIME"donner bash: !TIME": event not found.

Mais à la fois notify-sendet echotravailler avec"SYNC! TIME"

Quelqu'un peut-il expliquer pourquoi l' bash: !": event not founderreur apparaît?

Justice pour Monica
la source

Réponses:

31

!est le caractère d'extension de l'historique par défaut dans Bash, voir la section "EXPANSION D'HISTOIRE" dans la page de manuel Bash

  • L'expansion de l'historique n'a pas lieu si le !est entouré de guillemets simples, comme dans

    notify-send 'SYNC TIME!'
  • L'expansion de l'historique n'a pas lieu si le !est suivi d'un espace, d'une tabulation, d'un retour à la ligne, d'un retour chariot ou =, comme dans

    notify-send SYNC TIME!
  • L' expansion de l' histoire ne se déroulent dans

    echo "SYNC TIME!"

    Vous obtiendrez donc une erreur s'il n'y a pas de commande commençant par "dans votre historique

Florian Diesch
la source
4
Ce mauvais comportement peut être corrigé en ajoutant à votre .bashrcligne set +H. Notez que ce !n'est déjà pas spécial dans les scripts; le traiter comme spécial romprait de nombreux scripts conformes aux normes. Il n'est traité que comme "spécial" dans les shells interactifs, et uniquement par défaut jusqu'à ce que vous le corrigiez. :-)
R ..
15

Parce que dans bash, !est un mot réservé (OK, caractère), il a une signification particulière dans différents contextes. Dans ce cas particulier, vous ne respectez pas son importance dans la recherche d'histoire. De 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.

Fondamentalement, cela signifie que bash prendra les caractères après le !et recherchera dans votre historique la première commande qu'il trouve qui commence par ces caractères. Il est plus facile de démontrer que d'expliquer:

$ echo foo
foo
$ !e
echo foo
foo

L' !expansion de l'historique activée, qui correspondait à la première commande commençant par celle equi était exécutée précédemmentecho foo puis exécutée à nouveau. Donc, lorsque vous avez écrit "SYNC TIME!", bash a vu !", recherché dans l'historique une commande commençant par ", a échoué et s'en est plaint. Vous pouvez obtenir la même erreur en exécutant, par exemple !nocommandstartswiththis.

Pour imprimer un point d'exclamation, vous devez l'échapper de l'une des deux manières suivantes:

echo 'Hello world!'
echo Hello world\!
terdon
la source
6
echo Hello world!fonctionne --- l'expansion de l'histoire n'est pas déclenchée par des blancs ;-) (ah, les belles caisses de coin ...)
Rmano
1
Il vaut mieux compter sur l'échappement du point d'exclamation que sur les blancs.
Ehtesh Choudhury
1
@Rmano Je préfère le dire explicitement plutôt que de me guider d'un comportement qu'il est possible de changer
Braiam
!est un mot réservé dans bash, comme Posix déclare également . Mais je suis à peu près sûr que son statut est totalement indépendant de son rôle dans l'expansion de l'histoire et non pertinent pour son traitement entre guillemets. !est un mot réservé car il annule l'état de sortie d'une commande / pipeline, il ne peut donc pas être utilisé comme commande. Aucun autre mot réservé n'est spécial dans un "contexte entre guillemets, alors qu'il $n'est pas un mot réservé mais est traité spécialement.
Eliah Kagan