Como ecoar um estrondo!

59

Tentei criar um script echoinserindo o conteúdo em um arquivo, em vez de abri-lo com um editor

echo -e "#!/bin/bash \n /usr/bin/command args"  > .scripts/command

A saída :

bash:! / bin / bash: evento não encontrado

Eu isolei esse comportamento estranho ao estrondo .

$ echo !
!  

$ echo "!"
bash: !: event not found

$ echo \#!
#!

$ echo \#!/bin/bash
bash: !/bin/bash: event not found
  • Por que o bang está causando isso?
  • Quais são esses "eventos" aos quais o bash se refere?
  • Como superar esse problema e imprimir "#! / Bin / bash" na tela ou no meu arquivo?
Stefan
fonte

Respostas:

59

Tente usar aspas simples.

echo -e '#!/bin/bash \n /usr/bin/command args'  > .scripts/command

echo '#!'

echo '#!/bin/bash'

O problema está ocorrendo porque o bash está pesquisando em seu histórico! / Bin / bash. O uso de aspas simples evita esse comportamento.

Richm
fonte
3
Ele também desativa a interpolação de cadeias :(
Hubro 23/01
Essa é a questão! Você pode adicionar outros argumentos ao mesmo comando echo usando aspas duplas e obter interpolação nessas partes.
Richm 23/01
3
você também pode usar a string de estilo C no bash com $''. Por exemplo, echo $'1!\n2!\n3!\n'imprime cada número seguido por um estrondo e uma nova linha.
Spelufo
11
Colocar bang entre aspas simples não me ajudou ao inserir uma sequência de várias linhas:! citações festança-single não escapam estrondo (para real)
binki
11
@kbolino Você está certo. Portanto, a sequência de exemplo completa seria:, echo '0123'"$var"'456'observe dois pares de aspas simples e um par de aspas duplas.
Phyatt #
16

Como Richm disse , o bash está tentando fazer uma correspondência histórica . Outra maneira de evitá-lo é simplesmente escapar do estrondo com um \:

$ echo \#\!/bin/bash
#!/bin/bash

Embora esteja ciente de que, entre aspas duplas, o \não é removido:

$ echo "\!"
\!
Michael Mrozek
fonte
8
No bash, "\!"na verdade se expande para \!, não !, supostamente para conformidade com POSIX.
11117 Mikel
@Mikel Sigh. Tantas diferenças entre o zsh e o bash
Michael Mrozek
Isso funciona mesmo ao inserir uma seqüência de linhas múltiplas. Lembrar-se de 1. estar fora de uma string ao entrar no estrondo e 2. usar esse método (barra invertida) parece funcionar com a menor surpresa na maioria dos cenários.
binki
11
Seria útil observar que o bash não faz pesquisas de histórico ao executar um script, portanto, não é necessário fazer nada de especial nele.
binki
Não existe nenhuma sintaxe para simplesmente escapar entre !aspas duplas sem comportamento mágico? Isso é loucura ...
Lassi
13

!inicia uma substituição do histórico (um "evento" é uma linha no histórico de comandos); por exemplo, !lsexpande para a última linha de comando que contém lse !?fooexpande para a última linha de comando que contém foo. Você também pode extrair palavras específicas (por exemplo, !!:1refere-se à primeira palavra do comando anterior) e muito mais; veja o manual para detalhes.

Esse recurso foi inventado para recuperar rapidamente comandos anteriores nos dias em que a edição da linha de comandos era primitiva. Com shells modernos (pelo menos bash e zsh) e copiar e colar, a expansão do histórico não é tão útil quanto costumava ser - ainda é útil, mas você pode sobreviver sem ele.

Você pode alterar qual caractere aciona a substituição do histórico configurando a histcharsvariável; se você raramente usa a substituição do histórico, pode definir, por exemplo, histchars='¡^'para que ¡desencadeie a expansão do histórico em vez de !. Você pode até desativar o recurso completamente set +o histexpand.

Gilles 'SO- parar de ser mau'
fonte
3

Para poder desativar a expansão do histórico em uma linha de comando específica, você pode usar spacecomo o terceiro caractere de $histchars:

histchars='!^ '

Então, se você digitar seu comando com um espaço à esquerda, a expansão do histórico não será executada.

bash-4.3$ echo "#!/bin/bash"
bash: !/bin/bash: event not found
bash-4.3$  echo "#!/bin/bash"
#!/bin/bash

Observe, no entanto, que os espaços à esquerda também são usados ​​quando $HISTCONTROLcontém ignorespacecomo uma maneira de informar bashpara não registrar uma linha de comando no histórico.

Se você deseja que ambos os recursos sejam independentes, precisará escolher outro caractere como o terceiro caractere $histchars. Você quer um que não afete a maneira como seu comando é interpretado. Algumas opções:

  • using backslash ( \): \echo foofunciona, mas tem o efeito colateral de desativar aliases ou palavras-chave.
  • TAB: para inseri-lo na primeira posição, você precisa pressionar Ctrl+VTab.
  • Se você não se importa digitando duas chaves, você pode escolher qualquer personagem que normalmente não aparecer na primeira posição ( %, @, ?, escolher o seu próprio) e fazer um alias vazio para ele:

    histchars='!^%'
    alias %=
    

    Em seguida, insira esse caractere, espaço e seu comando:

    bash-4.3$ % echo !!
    !!
    

(você não poderá não gravar um comando em que a substituição do histórico tenha sido desativada. Observe também que o terceiro caractere padrão $histcharsé #para que a expansão do histórico não seja feita nos comentários. Se você o alterar e inserir comentários no prompt, você deve estar ciente do fato de que as sequências! podem ser expandidas lá).

Stéphane Chazelas
fonte
2

As soluções propostas não funcionam, por exemplo, no seguinte exemplo:

$ bash -c "echo 'hello World!'"
-bash: !'": event not found
$

Nesse caso, o estrondo pode ser impresso usando seu código ASCII octal:

$ bash -c "echo -e 'hello World\0041'"
hello World!
$
Atti
fonte
11
No seu primeiro comando, !entre aspas duplas, onde outras respostas indicam, mantém o significado da expansão do histórico. Você poderia usar bash -c 'echo '\''hello World!'\'ou bash -c "echo 'hello World"\!"'".
Gilles 'SO- stop be evil'
Sem o parâmetro -i, o ambiente pode ser alterado (como o seu ~ / .profile não é executado). No entanto, doing -i significa que qualquer saída do seu .profile será capturada na saída do comando que você realmente deseja executar.
Brent
-1

Desde o Bash 4.3, agora você pode usar aspas duplas para citar o caractere de expansão do histórico:

$ bash --version
GNU bash, version 4.3...
[...]
$ echo "Hello World!"
Hello World!
Flimm
fonte
4
Não, isso é !seguido apenas por "isso não é mais especial. echo "foo!"é OK, echo "foo!bar"não é
Stéphane Chazelas