Anführungszeichen innerhalb von $ (Befehlsersetzung) in Bash

126

In meiner Bash-Umgebung verwende ich Variablen, die Leerzeichen enthalten, und diese Variablen bei der Befehlssubstitution. Leider kann ich die Antwort auf SE nicht finden.

Wie zitiere ich meine Variablen richtig? Und wie soll ich es machen, wenn diese verschachtelt sind?

DIRNAME=$(dirname "$FILE")

oder zitiere ich außerhalb der Vertretung?

DIRNAME="$(dirname $FILE)"

oder beides?

DIRNAME="$(dirname "$FILE")"

oder benutze ich back-ticks?

DIRNAME=`dirname "$FILE"`

Was ist der richtige Weg, um dies zu tun? Und wie kann ich leicht überprüfen, ob die Anführungszeichen richtig eingestellt sind?

CousinCocaine
quelle
1
Dies ist eine gute Frage, aber warum sollten Sie sich angesichts aller Probleme mit eingebetteten Leerzeichen das Leben schwer machen, wenn Sie sie absichtlich verwenden?
Joe
3
@ Joe, mit eingebetteten Leerzeichen meinst du Platz in den Dateinamen? Persönlich benutze ich sie nicht so oft, aber ich arbeite mit Verzeichnissen und Dateien anderer Leute, bei denen ich nicht sicher bin, ob sie Leerzeichen enthalten. Außerdem denke ich, dass es besser ist, es sofort richtig zu machen, damit ich mir in Zukunft keine Sorgen mehr machen muss.
CousinCocaine
4
Ja. Was machen wir mit diesen "anderen Leuten"? <G>
Joe

Antworten:

188

In der Reihenfolge vom schlechtesten zum besten:

  • DIRNAME="$(dirname $FILE)" wird nicht tun, was Sie wollen, wenn $FILEWhitespace- oder Globbing-Zeichen enthalten \[?*.
  • DIRNAME=`dirname "$FILE"`ist technisch korrekt, aber Backticks werden für die Befehlserweiterung aufgrund der zusätzlichen Komplexität beim Verschachteln nicht empfohlen .
  • DIRNAME=$(dirname "$FILE")ist richtig, aber nur, weil dies eine Aufgabe ist . Wenn Sie die Befehlssubstitution in einem anderen Kontext verwenden, z. B. export DIRNAME=$(dirname "$FILE")oder du $(dirname "$FILE"), kann das Fehlen von Anführungszeichen zu Problemen führen, wenn das Ergebnis der Erweiterung Leerzeichen oder glühende Zeichen enthält.
  • DIRNAME="$(dirname "$FILE")"ist der empfohlene Weg. Sie können durch DIRNAME=einen Befehl und ein Leerzeichen ersetzen, ohne etwas anderes zu ändern, und dirnameerhalten die richtige Zeichenfolge.

Um noch weiter zu verbessern:

  • DIRNAME="$(dirname -- "$FILE")"funktioniert, wenn $FILEmit einem Bindestrich begonnen wird.
  • DIRNAME="$(dirname -- "$FILE"; printf x)" && DIRNAME="${DIRNAME%?x}"Funktioniert auch, wenn $FILEmit einem Zeilenumbruch geendet wird, da $()Zeilenumbrüche am Ende der Ausgabe dirnameabgeschnitten und nach dem Ergebnis ein Zeilenumbruch ausgegeben werden . Meine Güte dirname, warum musst du anders sein?

Sie können Befehlserweiterungen beliebig verschachteln. Wenn $()Sie immer einen neuen Angebotskontext erstellen, können Sie Folgendes tun:

foo "$(bar "$(baz "$(ban "bla")")")"

Sie wollen das nicht mit Backticks versuchen.

l0b0
quelle
3
Klare Antwort auf meine Frage. Wenn ich diese Variablen verschachtele, kann ich dann wie jetzt weiter zitieren?
CousinCocaine
3
Gibt es eine Referenz / Ressource, die das Verhalten von Anführungszeichen innerhalb einer Befehlssubstitution in Anführungszeichen beschreibt?
AmadeusDrZaius
12
@AmadeusDrZaius "Mit $()Ihnen erstellen Sie immer einen neuen Angebotskontext" , so ist es genau wie außerhalb der äußeren Anführungszeichen. Soweit ich weiß, steckt nichts mehr dahinter.
l0b0
4
@ l0b0 Danke, ja, ich fand deine Erklärung sehr klar. Ich habe mich nur gefragt, ob es auch irgendwo in einem Handbuch steht. Ich fand es (wenn auch inoffiziell) bei Wooledge . Ich denke, wenn Sie die Reihenfolge der Substitution sorgfältig lesen , können Sie diese Tatsache als Ergebnis ableiten.
AmadeusDrZaius
1
Verschachtelte Anführungszeichen sind also akzeptabel, lehnen uns jedoch ab, da die meisten Syntaxfarbschemata den besonderen Umstand nicht erkennen. Ordentlich.
Luke Davis
19

Sie können die Auswirkungen der variablen Anführungszeichen immer mit anzeigen printf.

Wortteilung durchgeführt am var1:

$ var1="hello     world"
$ printf '[%s]\n' $var1
[hello]
[world]

var1 zitiert, also keine Wortteilung:

$ printf '[%s]\n' "$var1"
[hello     world]

Wortteilung nach var1innen $(), entsprechend echo "hello" "world":

$ var2=$(echo $var1)
$ printf '[%s]\n' "$var2"
[hello world]

Keine Wortspaltung var1, kein Problem, wenn man Folgendes nicht zitiert $():

$ var2=$(echo "$var1")
$ printf '[%s]\n' "$var2"
[hello     world]

Das Wort spaltet sich var1wieder auf:

$ var2="$(echo $var1)"
$ printf '[%s]\n' "$var2"
[hello world]

Beides zu zitieren, ist der einfachste Weg, sicher zu sein.

$ var2="$(echo "$var1")"
$ printf '[%s]\n' "$var2"
[hello     world]

Globbing-Problem

Wenn eine Variable nicht in Anführungszeichen gesetzt wird, kann dies auch zu einer globalen Erweiterung ihres Inhalts führen:

$ mkdir test; cd test; touch file1 file2
$ var="*"
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]

Beachten Sie, dass dies nur geschieht, nachdem die Variable erweitert wurde. Es ist nicht erforderlich, während der Zuweisung einen Glob anzugeben:

$ var=*
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]

Verwenden Sie set -f, um dieses Verhalten zu deaktivieren:

$ set -f
$ var=*
$ printf '[%s]\n' $var
[*]

Und set +fum es wieder zu aktivieren:

$ set +f
$ printf '[%s]\n' $var
[file1]
[file2]
Graeme
quelle
8
Die Leute neigen dazu zu vergessen, dass das Teilen von Wörtern nicht das einzige Problem ist. Vielleicht möchten Sie Ihr Beispiel ändern var1='hello * world', um auch das Problem des Globbing zu veranschaulichen.
Stéphane Chazelas
10

Ergänzung zur akzeptierten Antwort:

Obwohl ich der Antwort von @ l0b0 hier im Allgemeinen zustimme , vermute ich, dass die Platzierung von Backticks in der "worst to best" -Liste zumindest teilweise auf die Annahme zurückzuführen $(...)ist, die überall verfügbar ist. Mir ist klar, dass die Frage Bash spezifiziert, aber es gibt viele Male, in denen sich herausstellt, dass Bash /bin/shnicht immer die vollständige Bourne Again-Shell ist.

Insbesondere weiß die Ebene Bourne - Shell nicht , was damit zu tun $(...), so Skripte , die mit ihm kompatibel seinem Anspruch (zB über eine #!/bin/shShebang - Zeile) wird wahrscheinlich schlecht benehmen , wenn sie von den „realen“ tatsächlich ausgeführt werden /bin/sh- dies ist von Spezielles Interesse, wenn beispielsweise Init-Skripte erstellt oder Pre- und Post-Skripte gepackt werden und diese bei der Installation eines Basissystems an einem überraschenden Ort landen können.

Wenn dies nach etwas klingt, das Sie mit dieser Variablen planen, ist das Verschachteln wahrscheinlich weniger problematisch, als wenn das Skript tatsächlich vorhersehbar ausgeführt wird. Wenn es sich um einen einfachen Fall handelt und die Portabilität ein Problem darstellt, neige ich häufig dazu, Backticks mit mehreren Zuweisungen zu verwenden, anstatt sie zu verschachteln , auch wenn ich davon ausgehe, dass das Skript normalerweise auf Systemen mit /bin/shBash ausgeführt wird.

Angesichts der Allgegenwart der implementierten Shells $(...)(Bash, Dash, et al.) Sind wir in der Lage, uns an die hübschere, einfacher zu verschachtelnde und in jüngerer Zeit bevorzugte POSIX-Syntax zu halten, z Alle Gründe, die @ l0b0 anführt.

Übrigens: Dies hat sich auch bei StackOverflow gelegentlich gezeigt -

rsandwick3
quelle
//, Hervorragende Antwort. Ich bin in der Vergangenheit auch auf Probleme mit der Abwärtskompatibilität mit / bin / sh gestoßen. Haben Sie einen Rat, wie Sie sein Problem mit abwärtskompatiblen Methoden lösen können?
Nathan Basanese
Nach meiner Erfahrung müssen Sie manchmal Backticks in Dollar-Paren umwandeln, und es hat noch nie geholfen (um genau zu sein), Dollar-Paren in Backticks umzuwandeln. Ich schreibe Backticks nur in meinem Javascript-Code und nicht in Shell-Code.
Steven Lu