Por que o ponto de exclamação entre aspas duplas causa um erro Bash?

25

Por favor, veja estes comandos:

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

Os dois primeiros comandos produzem um balão de notificação conforme o esperado. O terceiro dá o erro mostrado.

e

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

Aqui também, os echotrabalhos para os dois primeiros comandos, mas não para o terceiro.

Mais problemas aqui (embora eu não estivesse planejando usar isso): os dois notify-send "SYNC!TIME"e echo "SYNC!TIME"doe bash: !TIME": event not found.

Mas ambos notify-sende echotrabalhar com"SYNC! TIME"

Alguém pode explicar por que o bash: !": event not founderro aparece?

Justiça para Monica
fonte

Respostas:

31

!é o caractere padrão de expansão do histórico no Bash, consulte a seção "HISTORY EXPANSION" na página do Bash

  • A expansão do histórico não ocorre se !estiver entre aspas simples, como em

    notify-send 'SYNC TIME!'
  • A expansão do histórico não ocorre se !for seguido por um espaço, guia, nova linha, retorno de carro ou =, como em

    notify-send SYNC TIME!
  • Expansão história faz ocorrem em

    echo "SYNC TIME!"

    Portanto, você receberá um erro se não houver um comando começando "no seu histórico

Florian Diesch
fonte
4
Esse mau comportamento pode ser corrigido adicionando à sua .bashrclinha set +H. Observe que !já não é especial em scripts; tratá-lo como especial quebraria muitos scripts em conformidade com os padrões. Ele é tratado apenas como "especial" em shells interativos e somente por padrão até você corrigi-lo. :-)
R. ..
15

Como no bash, !é uma palavra reservada (OK, caractere), ela possui um significado especial em diferentes contextos. Nesse caso em particular, você está afetando sua importância na pesquisa de histórico. 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.

Basicamente, o que isso significa é que o bash pegará os caracteres após o !e pesquisará em seu histórico o primeiro comando encontrado, que começa com esses caracteres. É mais fácil demonstrar do que explicar:

$ echo foo
foo
$ !e
echo foo
foo

A !expansão do histórico ativado, que correspondeu ao primeiro comando, começando com o eque foi executado anteriormente, echo fooque foi executado novamente. Então, quando você escreveu "SYNC TIME!", o bash viu !", procurou no histórico por um comando começando com ", falhou e reclamou. Você pode obter o mesmo erro executando, por exemplo !nocommandstartswiththis.

Para imprimir um ponto de exclamação, você precisa escapá-lo de uma destas duas maneiras:

echo 'Hello world!'
echo Hello world\!
Terdon
fonte
6
echo Hello world!O trabalho --- expansão a história não é desencadeada por espaços em branco ;-) (ah, os casos de canto agradável ...)
Rmano
1
É melhor confiar em escapar do ponto de exclamação do que em branco.
Ehtesh Choudhury
1
@Rmano prefiro dizer explicitamente então ao invés de guia-me de um comportamento que é possível a mudança
Braiam
!é uma palavra reservada no bash, como POSIX também declara . Mas tenho certeza de que seu status é completamente independente do seu papel na expansão da história e irrelevante para o tratamento entre aspas duplas. !é uma palavra reservada porque nega o status de saída de um comando / pipeline, portanto, não pode ser usada como um comando. Nenhuma outra palavra reservada é especial em um "contexto entre aspas, enquanto não$ é uma palavra reservada, mas é tratada especialmente.
Eliah Kagan