Warum stört das Ausrufezeichen "!" Manchmal?

13

Mir ist klar, dass dies !im Kontext des Befehlszeilenverlaufs eine besondere Bedeutung für die Befehlszeile hat, aber abgesehen davon kann das Ausrufezeichen in einem ausgeführten Skript manchmal einen Parsing-Fehler verursachen.
Ich denke, es hat etwas mit einer zu tun event, aber ich habe keine Ahnung, was eine Veranstaltung ist oder was sie tut. Trotzdem kann sich ein Befehl in verschiedenen Situationen unterschiedlich verhalten.
Das letzte Beispiel unten verursacht einen Fehler. aber warum, wenn derselbe Code außerhalb der Befehlsersetzung funktioniert hat? .. mit GNU bash 4.1.5

# This works, with or without a space between ! and p
  { echo -e "foo\nbar" | sed -nre '/foo/! p'
    echo -e "foo\nbar" | sed -nre '/foo/!p'; }
# bar
# bar

# This works, works when there is a space between ! and p
  var="$(echo -e "foo\nbar" | sed -nre '/foo/! p')"; echo "$var"
# bar

# This causes an ERROR, with NO space between ! and p
  var="$(echo -e "foo\nbar" | sed -nre '/foo/!p')"; echo "$var"
# bash: !p': event not found
Peter.O
quelle
@Warren .. Danke. Ich hatte diese Qualitätssicherung gesehen, aber sie spricht nur darüber, wie man dem Backslash entgeht ... Mein Problem bezieht sich eher darauf, warum der scheinbar bereits entkommene Code in einer Situation und nicht in einer anderen
funktioniert
@fred: "scheinbar schon entkommen"? Ich sehe überhaupt keine Flucht und Sie verwenden doppelte Anführungszeichen. Siehe meine (überarbeitete) Antwort. Welcher Teil ist Ihrer Meinung nach entkommen?
Caleb
@Caleb. Ja, ich habe den falschen Begriff verwendet. protectedWäre angemessener gewesen. (geschützt durch einfache Anführungszeichen)
Peter.O
Wenn Sie sich nur mit einfachen Aufgaben befassen, können Sie var=$(…)(keine doppelten Anführungszeichen) verwenden, und es wird so funktionieren, wie Sie es erwarten (glaube ich). Dies ist immer noch „sicher“ , weil der Wert Teil einer einfache Zuordnung ist nicht Gegenstand Wort Spaltung oder Globbing (obwohl dies nicht die Zuordnungen wahr sein kann durch builtins erfolgen (zB export, localusw.) unter allen Schalen). Leider geht dies nicht über einfache Zuweisungen hinaus, da die doppelten Anführungszeichen den Schutz vor Worttrennung und Globbing bieten und andere Arten der Erweiterung in anderen Kontexten ermöglichen.
Chris Johnsen

Antworten:

11

Der !Charakter ruft die Ersetzung der Bash-Historie auf. Nach einer Zeichenfolge (wie in Ihrem fehlerhaften Beispiel) wird versucht, auf das letzte Verlaufsereignis zu expandieren, das mit dieser Zeichenfolge begonnen hat. So wie es $varauf den Wert dieser Zeichenfolge !echoerweitert wird, wird es auf den letzten Echo-Befehl in Ihrem Verlauf erweitert.

Der Weltraum ist in solchen Erweiterungen ein brechender Charakter. Beachten Sie zunächst, wie dies mit Variablen funktionieren würde:

# var="like"
# echo "$var"
like
# echo "$"
$
# echo "Do you $var frogs?"
Do you like frogs?       <- as expected, variable name broken at space
# echo "Do you $varfrogs?"
Do you?                  <- $varfrogs not defined, replaced with blank
# echo "Do you $ var frogs?"
Do you $ var frogs?      <- $ not a valid variable name, ignored

Dasselbe wird für die Erweiterung der Geschichte passieren. Das Bang-Zeichen ( !) beginnt mit einer History-Ersetzungssequenz, jedoch nur, wenn eine Zeichenfolge folgt. Wenn Sie ein Leerzeichen darauf setzen, wird es buchstäblich als Teil einer Ersetzungssequenz ausgegeben.

Sie können diese Art des Ersetzens sowohl für Variablen als auch für Verlaufserweiterungen vermeiden, indem Sie einfache Anführungszeichen verwenden. Ihre ersten Beispiele verwendeten einfache Anführungszeichen und liefen so gut. Ihre letzten Beispiele stehen in doppelten Anführungszeichen und wurden daher von Bash nach Expansionssequenzen durchsucht, bevor etwas anderes ausgeführt wurde. Der einzige Grund, warum der erste nicht ausgelöst hat, ist, dass das Leerzeichen ein Unterbrechungszeichen ist, wie oben gezeigt.

Caleb
quelle
Vielen Dank, Caleb. Eine andere meiner Vorurteile wurde abgewiesen. Ich dachte, dass die Bash-Analyse von der innersten Klammer oder Klammer aus durchgeführt wurde und dann nach außen lief.
Peter.O
1
Das Zitieren in Bash ist verwirrend genug, ohne dass es sich in verschachtelten Zeichenfolgen ändert. Der Austausch erfolgt also sehr früh im Prozess. Betrachten Sie dieses Beispiel:var=word; echo "test '$var'"; echo 'test "$var"'
Caleb
Ja, verstanden. Mir war bewusst, dass ich Anführungszeichen in Anführungszeichen einfügte ... Mein Missverständnis war, dass ich dachte, der Code in den Klammern der Befehlsersetzung würde getrennt analysiert werden, je nachdem, was diese Klammern umgibt. aber anscheinend nicht .. danke.
Peter.O
6

Wie schon von Caleb gesagt , !wird verwendet, um die Ersetzung der Bash-Historie aufzurufen.

Wenn Sie wie ich das Gefühl haben, dass Sie eine solche Funktion nicht benötigen, können Sie sie deaktivieren, indem Sie die folgende Zeile einfügen ~/.bashrc:

set +H

Ich brauche es nicht , weil die Geschichte durch den Pfeil nach oben und zurückgewonnen werden Ctrl- rinkrementelle Rückwärtssuche. Eine detaillierte Liste der Verknüpfungen finden Sie in der bash-Handbuchseite im Abschnitt Befehle zum Bearbeiten des Verlaufs.

Enzotib
quelle
2
Wie lebst du ohne !!?
Caleb
Danke. Ich würde denken, dass dies ein Problem sein könnte, was die Portabilität set +H
betrifft
2
@fred: Seltsam, im Allgemeinen ist die History-Erweiterung nur für interaktive Shells aktiviert.
Enzotib
@enzo .. Nochmals vielen Dank .. Ich hatte es von der Kommandozeile aus getestet .. Ah! Wenn das Lernen nicht so viel Spaß macht, wäre es langweilig ... habe ich schon Kaffee erwähnt? es hilft auch :)
Peter.O
Ja, das ist eine GOTCHA. Ich habe den eingefügten Code aus Skripten kopiert, die aus diesem Grund in der Befehlszeile fehlgeschlagen sind. Die Erweiterung des Verlaufs war im Skript kein Problem, befindet sich jedoch in einer interaktiven Shell.
Caleb
2

Ihr erstes Beispiel:

{ echo -e "foo\nbar" | sed -nre '/foo/! p'
    echo -e "foo\nbar" | sed -nre '/foo/!p'; }

könnte auf reduziert werden

echo '! p' 
echo '!p'

In einfachen Anführungszeichen behalten alle Zeichen ihre Literalwerte bei. Dadurch !hat seine besondere Bedeutung verloren und die geschichtliche Erweiterung ist nicht vorgeformt.

Ihr zweites und drittes Beispiel:

var="$(echo -e "foo\nbar" | sed -nre '/foo/! p')"; echo "$var"

var="$(echo -e "foo\nbar" | sed -nre '/foo/!p')"; echo "$var"

könnte auf reduziert werden

echo "'! p'"

echo "'!p'"

'! p'und '!p'sind im Wesentlichen Teile der doppelt zitierten Zeichenfolgen.

In doppelte Anführungszeichen, erhalten alle Charaktere ihre Literalwerte ausnehmen $ , `, \und !.

Dies impliziert, dass die einfachen Anführungszeichen von '! p'und '!p'ihre spezielle Bedeutung verloren haben (dh nicht in der Lage zu fliehen !), aber !dennoch ihre spezielle Bedeutung behalten, sodass die Geschichtserweiterung durchgeführt wird.

Wenn !jedoch ein Leerzeichen folgt, wird keine Verlaufserweiterung durchgeführt.

Zitat aus man bash:

ZITIEREN

[...]

Wenn Sie Zeichen in einfache Anführungszeichen setzen, bleibt der Literalwert jedes Zeichens in den Anführungszeichen erhalten. [...]

Das Einschließen von Zeichen in doppelte Anführungszeichen behält den Literalwert aller Zeichen in den Anführungszeichen bei, mit Ausnahme von $, `, \ und, wenn die Verlaufserweiterung aktiviert ist,!. [...] Wenn diese Option aktiviert ist, wird die Verlaufserweiterung ausgeführt, es sei denn, ein! Das Erscheinen in doppelten Anführungszeichen wird mit einem Backslash abgeschlossen. Der Backslash vor dem! wird nicht entfernt.

GESCHICHTE ERWEITERUNG

[...]

Geschichtserweiterungen werden durch das Erscheinen des Geschichtserweiterungscharakters eingeleitet. standardmäßig. Nur Backslash (\) und einfache Anführungszeichen können das Verlaufserweiterungszeichen zitieren.

Mehrere Zeichen verhindern die Verlaufserweiterung, wenn sie unmittelbar nach dem Verlaufserweiterungszeichen gefunden werden, auch wenn sie nicht in Anführungszeichen stehen: Leerzeichen, Tabulator, Zeilenvorschub, Wagenrücklauf und =. Wenn die Option extglob shell aktiviert ist, wird die Erweiterung ebenfalls verhindert.

cychoi
quelle