Wie man einen Knall hallt!

59

Ich habe versucht, ein Skript zu erstellen, indem ich echoden Inhalt in eine Datei geschrieben habe, anstatt ihn mit einem Editor zu öffnen

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

Die Ausgabe :

Bash:! / bin / Bash: Ereignis nicht gefunden

Ich habe dieses seltsame Verhalten auf den Knall beschränkt .

$ echo !
!  

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

$ echo \#!
#!

$ echo \#!/bin/bash
bash: !/bin/bash: event not found
  • Warum verursacht Knall das?
  • Was sind diese "Ereignisse", auf die sich Bash bezieht?
  • Wie komme ich an diesem Problem vorbei und drucke "#! / Bin / bash" auf den Bildschirm oder meine Datei?
Stefan
quelle

Antworten:

59

Versuchen Sie es mit einfachen Anführungszeichen.

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

echo '#!'

echo '#!/bin/bash'

Das Problem tritt auf, weil bash den Verlauf nach! / Bin / bash durchsucht. Die Verwendung von einfachen Anführungszeichen verhindert dieses Verhalten.

Richm
quelle
3
Es deaktiviert auch die String-Interpolation :(
Hubro
Das ist der Punkt! Sie können demselben Echo-Befehl mit doppelten Anführungszeichen weitere Argumente hinzufügen und Interpolation für diese Teile erhalten.
Richm
3
Sie können in bash with auch eine Zeichenfolge im C-Stil verwenden $''. Zum Beispiel echo $'1!\n2!\n3!\n'druckt jede Zahl, gefolgt von einem Knall und einem Zeilenumbruch.
Spelufo
1
Bei der Eingabe eines mehrzeiligen Strings hat es mir nicht geholfen, in einfache Anführungszeichen zu setzen:! Bash - einfache Anführungszeichen nicht entgehen Knall (für echte)
binki
1
@ kbolino Du hast recht. Die vollständige Beispielzeichenfolge wäre also:, echo '0123'"$var"'456'notiere zwei Paare einfacher Anführungszeichen und ein Paar doppelter Anführungszeichen.
Phyatt
16

Wie Richm sagte , versucht Bash, ein Historienspiel zu machen . Eine andere Möglichkeit, dies zu vermeiden, besteht darin, einfach dem Knall zu entkommen \:

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

Beachten Sie, dass in Anführungszeichen Folgendes \nicht entfernt wird:

$ echo "\!"
\!
Michael Mrozek
quelle
8
In der bash "\!"erweitert eigentlich \!nicht !, angeblich für POSIX - Konformität.
Mikel
@Mikel Seufzer So viele Unterschiede zwischen zsh und bash
Michael Mrozek
Dies funktioniert auch bei Eingabe einer mehrzeiligen Zeichenfolge. Es scheint in den meisten Szenarien am wenigsten überraschend zu sein, 1. sich bei der Eingabe des Knalls außerhalb eines Strings zu befinden und 2. diese Methode (Backslash) zu verwenden.
binki
1
Es wäre nützlich zu bemerken, dass bash beim Ausführen eines Skripts keine Verlaufssuche durchführt, daher müssen Sie dort nichts Besonderes dafür tun.
binki
Gibt es keine Syntax, um einfach !in doppelten Anführungszeichen ohne magisches Verhalten zu fliehen ? Das ist verrückt ...
Lassi
13

!Startet eine Historiensubstitution (ein „Ereignis“ ist eine Zeile in der Kommandogeschichte); Zum Beispiel wird !lsauf die letzte Befehlszeile mit lsund !?fooauf die letzte Befehlszeile mit erweitert foo. Sie können auch bestimmte Wörter extrahieren ( !!:1bezieht sich z. B. auf das erste Wort des vorherigen Befehls) und mehr. Einzelheiten finden Sie im Handbuch.

Diese Funktion wurde erfunden, um frühere Befehle in den Tagen, als die Befehlszeilen-Edition noch primitiv war, schnell wieder aufzurufen. Mit modernen Shells (mindestens bash und zsh) und Copy-and-Paste ist die Verlaufserweiterung nicht mehr so ​​nützlich wie früher - sie ist immer noch hilfreich, aber Sie können ohne sie auskommen.

Sie können ändern, welches Zeichen die Ersetzung des Verlaufs auslöst, indem Sie die histcharsVariable festlegen. Wenn Sie die Protokollersetzung selten verwenden, können Sie z. B. festlegen histchars='¡^', dass ¡die Protokollerweiterung statt ausgelöst wird !. Sie können die Funktion mit sogar ganz ausschalten set +o histexpand.

Gilles 'SO - hör auf böse zu sein'
quelle
3

Um die Verlaufserweiterung in einer bestimmten Befehlszeile deaktivieren zu können, können Sie spaceals 3. Zeichen Folgendes verwenden $histchars:

histchars='!^ '

Wenn Sie dann Ihren Befehl mit einem führenden Leerzeichen eingeben, wird die Verlaufserweiterung nicht ausgeführt.

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

Beachten Sie jedoch, dass führende Leerzeichen auch verwendet werden, wenn sie $HISTCONTROLenthalten ignorespace, um bashzu verhindern, dass eine Befehlszeile im Verlauf aufgezeichnet wird.

Wenn Sie beide Funktionen unabhängig voneinander verwenden möchten, müssen Sie ein anderes Zeichen als 3. Zeichen von auswählen $histchars. Sie möchten eine, die sich nicht auf die Art und Weise auswirkt, wie Ihr Befehl interpretiert wird. Ein paar Möglichkeiten:

  • Verwenden von Backslash ( \): \echo foofunktioniert, hat aber den Nebeneffekt, dass Aliase oder Schlüsselwörter deaktiviert werden.
  • TAB: Um es an der ersten Position einzufügen, müssen Sie allerdings drücken Ctrl+VTab.
  • wenn Sie nichts dagegen haben zwei Tasten eingeben, können Sie ein beliebiges Zeichen auswählen , die normalerweise nicht in der ersten Position angezeigt wird ( %, @, ?, Ihre eigenen Pick) und einen leeren Alias für sie machen:

    histchars='!^%'
    alias %=

    Geben Sie dann das Zeichen, das Leerzeichen und Ihren Befehl ein:

    bash-4.3$ % echo !!
    !!

(Sie können jedoch keinen Befehl aufzeichnen, bei dem die Protokollersetzung deaktiviert wurde. Beachten Sie außerdem, dass das dritte Standardzeichen von nicht in Kommentaren angezeigt $histcharswird #. Wenn Sie es ändern, geben Sie Kommentare in das Feld ein sollten Sie sich darüber im Klaren sein, dass! Sequenzen dort erweitert werden können).

Stéphane Chazelas
quelle
2

Die vorgeschlagenen Lösungen funktionieren nicht, z. B. im folgenden Beispiel:

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

In diesem Fall kann der Knall mit seinem oktalen ASCII-Code gedruckt werden:

$ bash -c "echo -e 'hello World\0041'"
hello World!
$
Atti
quelle
1
In Ihrem ersten Befehl steht das !zwischen doppelten Anführungszeichen, wobei andere Antworten darauf hinweisen, dass es seine historische Erweiterungsbedeutung beibehält. Sie könnten bash -c 'echo '\''hello World!'\'oder verwenden bash -c "echo 'hello World"\!"'".
Gilles 'SO- hör auf böse zu sein'
Ohne den Parameter -i kann die Umgebung geändert werden (so als würde Ihr ~ / .profile nicht ausgeführt). -I bedeutet jedoch, dass alle Ausgaben Ihres .profile in der Ausgabe des Befehls erfasst werden, den Sie tatsächlich ausführen möchten.
Brent
-1

Seit Bash 4.3 können Sie doppelte Anführungszeichen verwenden, um das Verlaufserweiterungszeichen zu zitieren:

$ bash --version
GNU bash, version 4.3...
[...]
$ echo "Hello World!"
Hello World!
Flimm
quelle
4
Nein, darauf !folgt nur, dass das "nichts Besonderes mehr ist. echo "foo!"ist in Ordnung, echo "foo!bar"ist nicht
Stéphane Chazelas