Comment faire écho à un coup!

59

J'ai essayé de créer un script en echo'insérant le contenu dans un fichier, au lieu de l'ouvrir avec un éditeur

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

La sortie :

bash:! / bin / bash: événement non trouvé

J'ai isolé ce comportement étrange au bang .

$ echo !
!  

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

$ echo \#!
#!

$ echo \#!/bin/bash
bash: !/bin/bash: event not found
  • Pourquoi est-ce que bang est la cause?
  • Quels sont ces "événements" auxquels bash fait référence?
  • Comment résoudre ce problème et imprimer "#! / Bin / bash" à l'écran ou dans mon fichier?
Stefan
la source

Réponses:

59

Essayez d'utiliser des guillemets simples.

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

echo '#!'

echo '#!/bin/bash'

Le problème se produit parce que bash cherche dans son historique! / Bin / bash. L'utilisation de guillemets simples échappe à ce comportement.

Richm
la source
3
Il désactive également l'interpolation de chaîne :(
Hubro, le
C'est le but! Vous pouvez ajouter d'autres arguments à la même commande echo en utilisant des guillemets doubles et obtenir une interpolation sur ces parties.
Richm
3
vous pouvez également utiliser la chaîne de style C dans bash avec $''. Par exemple, echo $'1!\n2!\n3!\n'imprime chaque numéro suivi d'un bang et d'une nouvelle ligne.
Spelufo
1
Mettre bang entre guillemets simples ne m'a pas aidé lors de la saisie d'une chaîne multiligne:! bash - les guillemets simples n'échappent pas au bang (pour de vrai)
binki
1
@ kbolino Vous avez raison. Ainsi, la chaîne d’exemple complet serait:, echo '0123'"$var"'456'notez deux paires de guillemets simples et une paire de guillemets doubles.
Phyatt
16

Comme Richm l'a dit , bash essaie de faire une correspondance d'histoire . Une autre façon de l'éviter est simplement d'échapper au bang avec un \:

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

Attention, entre guillemets doubles, le \n'est pas supprimé:

$ echo "\!"
\!
Michael Mrozek
la source
8
En bash, "\!"étend réellement à \!, pas !soi-disant pour la conformité POSIX.
Mikel
@ Mikel Sigh. Tant de différences entre zsh et bash
Michael Mrozek
Cela fonctionne même lorsque vous entrez une chaîne multiligne. Se souvenir de 1. être en dehors d'une chaîne lorsque vous entrez dans le bang et 2. utiliser cette méthode (barre oblique inverse) semble fonctionner avec la moindre surprise dans la plupart des scénarios.
binki
1
Il serait utile de noter que bash ne fait pas de recherches d’historique lors de l’exécution d’un script, il n’est donc pas nécessaire de faire quelque chose de spécial pour cela.
binki
N'y a-t-il pas une syntaxe pour simplement échapper aux !guillemets sans comportement magique? C'est fou ...
Lassi
13

!commence une substitution d'historique (un «événement» est une ligne de l'historique des commandes); par exemple, !lsétend jusqu'à la dernière ligne de commande contenant lset !?fooélargit jusqu'à la dernière ligne de commande contenant foo. Vous pouvez également extraire des mots spécifiques (par exemple, !!:1fait référence au premier mot de la commande précédente) et plus encore; voir le manuel pour plus de détails.

Cette fonctionnalité a été inventée pour rappeler rapidement les commandes précédentes à l’époque où l’édition en ligne de commande était primitive. Avec les shells modernes (au moins bash et zsh) et le copier-coller, le développement de l'historique n'est plus aussi utile qu'auparavant: il est toujours utile, mais vous pouvez vous en passer.

Vous pouvez changer le personnage qui déclenche la substitution d'historique en définissant la histcharsvariable. si vous utilisez rarement la substitution d'historique, vous pouvez définir, par exemple histchars='¡^', le ¡déclenchement de l'expansion de l'historique au lieu de !. Vous pouvez même désactiver complètement la fonctionnalité avec set +o histexpand.

Gilles, arrête de faire le mal
la source
3

Pour pouvoir désactiver le développement de l'historique sur une ligne de commande particulière, vous pouvez utiliser spacele 3ème caractère de $histchars:

histchars='!^ '

Ensuite, si vous entrez votre commande avec un espace de début, l'expansion de l'historique ne sera pas effectuée.

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

Notez cependant que les espaces de début sont également utilisés quand $HISTCONTROLcontient contient ignorespacecomme un moyen de bashne pas enregistrer une ligne de commande dans l'historique.

Si vous souhaitez que les deux fonctionnalités soient indépendantes, vous devez choisir un autre caractère en tant que 3ème caractère de $histchars. Vous en voulez un qui n'affecte pas la façon dont votre commande est interprétée. Quelques options:

  • using backslash ( \): \echo foofonctionne mais cela a pour effet secondaire de désactiver les alias ou les mots-clés.
  • ONGLET: pour l'insérer en première position, vous devez appuyer sur Ctrl+VTab.
  • si vous ne me dérange pas taper deux touches, vous pouvez choisir un caractère qui ne semble pas normalement en première position ( %, @, ?, choisissez votre propre) et faire un alias vide pour elle:

    histchars='!^%'
    alias %=

    Ensuite, entrez ce caractère, espace et votre commande:

    bash-4.3$ % echo !!
    !!

(vous ne pourrez pas ne pas enregistrer une commande dont la substitution d’historique a été désactivée. Notez également que le 3ème caractère par défaut $histcharsest le #développement de l’historique ne se fait pas dans les commentaires. Si vous le modifiez et que vous entrez des commentaires à l’écran vous devez être conscient du fait que les séquences peuvent être étendues là-bas).

Stéphane Chazelas
la source
2

Les solutions proposées ne fonctionnent pas, par exemple, dans l'exemple suivant:

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

Dans ce cas, le bang peut être imprimé avec son code ASCII octal:

$ bash -c "echo -e 'hello World\0041'"
hello World!
$
Atti
la source
1
Dans votre première commande, le !signe is se situe entre guillemets, où, comme d’autres réponses, il conserve son sens de développement de l’historique. Vous pouvez utiliser bash -c 'echo '\''hello World!'\'ou bash -c "echo 'hello World"\!"'".
Gilles 'SO- arrête d'être méchant'
Sans le paramètre -i, l'environnement peut être modifié (par exemple, votre fichier ~ / .profile ne s'exécute pas). Cependant, faire -i signifie que toute sortie de votre fichier .profile sera capturée dans la sortie de la commande que vous voulez réellement exécuter.
Brent
-1

Depuis Bash 4.3, vous pouvez maintenant utiliser des guillemets doubles pour citer le caractère d'expansion de l'historique:

$ bash --version
GNU bash, version 4.3...
[...]
$ echo "Hello World!"
Hello World!
Flimm
la source
4
Non, c'est seulement !suivi par "ce n'est plus spécial. echo "foo!"va bien, echo "foo!bar"n'est pas
Stéphane Chazelas