Wann wird eine Shell-Variable in Anführungszeichen gesetzt?

181

Könnte mir jemand sagen, ob ich Variablen in einem Shell-Skript in Anführungszeichen setzen soll oder nicht?

Ist beispielsweise Folgendes richtig:

xdg-open $URL 
[ $? -eq 2 ]

oder

xdg-open "$URL"
[ "$?" -eq "2" ]

Und wenn ja, warum?

Cristian
quelle
Diese Frage enthält viele Duplikate, von denen sich viele nicht mit Variablen befassen. Daher habe ich den Titel "Wert" anstelle von "Variable" verwendet. Ich hoffe, dies hilft mehr Menschen, dieses Thema zu finden.
Tripleee
1
@codeforester Was ist mit der rückgängig gemachten Bearbeitung los?
Tripleee

Antworten:

128

Allgemeine Regel: Zitieren Sie es, wenn es entweder leer sein kann oder Leerzeichen (oder wirklich Leerzeichen) oder Sonderzeichen (Platzhalter) enthalten kann. Das Nicht-Zitieren von Zeichenfolgen mit Leerzeichen führt häufig dazu, dass die Shell ein einzelnes Argument in viele zerlegt.

$?benötigt keine Anführungszeichen, da es sich um einen numerischen Wert handelt. Ob es $URLbenötigt wird, hängt davon ab, was Sie dort zulassen und ob Sie noch ein Argument wünschen, wenn es leer ist.

Ich neige dazu, Zeichenfolgen immer nur aus Gewohnheit zu zitieren, da dies sicherer ist.

paxdiablo
quelle
2
Beachten Sie, dass "Leerzeichen" wirklich "Leerzeichen" bedeuten.
William Pursell
4
@Cristian: Wenn Sie nicht sicher sind, was in der Variablen enthalten sein könnte, ist es sicherer, sie zu zitieren. Ich neige dazu, dem gleichen Prinzip wie paxdiablo zu folgen und es mir zur Gewohnheit zu machen, alles zu zitieren (es sei denn, es gibt einen bestimmten Grund, dies nicht zu tun).
Gordon Davisson
11
Wenn Sie den Wert von IFS nicht kennen, zitieren Sie ihn, egal was passiert. Wenn ja IFS=0, echo $?kann das sehr überraschend sein.
Charles Duffy
3
Zitat basierend auf dem Kontext, nicht basierend auf dem, was Sie von den Werten erwarten, sonst werden Ihre Fehler schlimmer. Zum Beispiel, sind Sie sicher , dass keine Ihrer Pfade Räume haben, so dass Sie denken Sie schreiben können cp $source1 $source2 $dest, aber wenn für einige unerwarteten Grund destnicht gesetzt bekommt, nur das dritte Argument verschwindet, und es wird still kopieren source1über source2Sie stattdessen eine des Gebens entsprechender Fehler für das leere Ziel (wie es der Fall gewesen wäre, wenn Sie jedes Argument zitiert hätten).
Derek Veit
3
quote it if...hat den Denkprozess rückwärts - Anführungszeichen fügen Sie nicht hinzu, wenn Sie sie benötigen, sondern entfernen sie, wenn Sie sie benötigen. Immer Strings und Skripte in einfache Anführungszeichen wickeln , es sei denn Sie müssen doppelte Anführungszeichen verwenden (zB lassen eine Variable erweitern) oder Notwendigkeit keine Anführungszeichen zu verwenden (zB zu tun Globbing und Dateinamen Expansion).
Ed Morton
90

Kurz gesagt, zitieren Sie alles, wo Sie die Shell nicht benötigen, um Token-Splitting und Wildcard-Erweiterung durchzuführen.

Einfache Anführungszeichen schützen den Text zwischen ihnen wörtlich. Es ist das richtige Werkzeug, wenn Sie sicherstellen müssen, dass die Shell die Saite überhaupt nicht berührt. In der Regel ist dies der Anführungsmechanismus der Wahl, wenn Sie keine variable Interpolation benötigen.

$ echo 'Nothing \t in here $will change'
Nothing \t in here $will change

$ grep -F '@&$*!!' file /dev/null
file:I can't get this @&$*!! quoting right.

Doppelte Anführungszeichen sind geeignet, wenn eine variable Interpolation erforderlich ist. Mit geeigneten Anpassungen ist es auch eine gute Problemumgehung, wenn Sie einfache Anführungszeichen in der Zeichenfolge benötigen. (Es gibt keine einfache Möglichkeit, ein einfaches Anführungszeichen zwischen einfachen Anführungszeichen zu umgehen, da es in einfachen Anführungszeichen keinen Escape-Mechanismus gibt. Wenn dies der Fall wäre, würden sie nicht vollständig wörtlich zitieren.)

$ echo "There is no place like '$HOME'"
There is no place like '/home/me'

Keine Anführungszeichen sind geeignet, wenn die Shell speziell eine Token-Aufteilung und / oder Wildcard-Erweiterung durchführen soll.

Token-Aufteilung;

 $ words="foo bar baz"
 $ for word in $words; do
 >   echo "$word"
 > done
 foo
 bar
 baz

Im Gegensatz:

 $ for word in "$words"; do echo "$word"; done
 foo bar baz

(Die Schleife wird nur einmal über die einzelne Zeichenfolge in Anführungszeichen ausgeführt.)

 $ for word in '$words'; do echo "$word"; done
 $words

(Die Schleife wird nur einmal über die wörtliche Zeichenfolge in einfachen Anführungszeichen ausgeführt.)

Platzhaltererweiterung:

$ pattern='file*.txt'
$ ls $pattern
file1.txt      file_other.txt

Im Gegensatz:

$ ls "$pattern"
ls: cannot access file*.txt: No such file or directory

(Es gibt keine wörtlich benannte Datei file*.txt.)

$ ls '$pattern'
ls: cannot access $pattern: No such file or directory

(Es gibt auch keine Datei mit dem Namen $pattern!)

Konkreter sollte alles, was einen Dateinamen enthält, normalerweise in Anführungszeichen gesetzt werden (da Dateinamen Leerzeichen und andere Shell-Metazeichen enthalten können). Alles, was eine URL enthält, sollte normalerweise in Anführungszeichen gesetzt werden (da viele URLs Shell-Metazeichen wie ?und enthalten &). Alles, was einen regulären Ausdruck enthält, sollte normalerweise zitiert werden (dito dito). Alles, was signifikante Leerzeichen außer einzelnen Leerzeichen zwischen Nicht-Leerzeichen enthält, muss in Anführungszeichen gesetzt werden (da die Shell sonst das Leerzeichen effektiv in einzelne Leerzeichen einfügt und alle führenden oder nachfolgenden Leerzeichen abschneidet).

Wenn Sie wissen, dass eine Variable nur einen Wert enthalten kann, der keine Shell-Metazeichen enthält, ist das Zitieren optional. Ein nicht zitiertes Element $?ist also grundsätzlich in Ordnung, da diese Variable immer nur eine einzige Zahl enthalten kann. Es "$?"ist jedoch auch korrekt und wird aus Gründen der allgemeinen Konsistenz und Korrektheit empfohlen (obwohl dies meine persönliche Empfehlung ist, keine allgemein anerkannte Richtlinie).

Werte, die keine Variablen sind, folgen grundsätzlich denselben Regeln, obwohl Sie dann auch alle Metazeichen umgehen können, anstatt sie in Anführungszeichen zu setzen. In einem allgemeinen Beispiel wird eine URL mit einem &darin von der Shell als Hintergrundbefehl analysiert, es sei denn, das Metazeichen wird maskiert oder in Anführungszeichen gesetzt:

$ wget http://example.com/q&uack
[1] wget http://example.com/q
-bash: uack: command not found

(Dies geschieht natürlich auch, wenn sich die URL in einer Variablen ohne Anführungszeichen befindet.) Für eine statische Zeichenfolge sind einfache Anführungszeichen am sinnvollsten, obwohl hier jede Form von Anführungszeichen oder Escapezeichen funktioniert.

wget 'http://example.com/q&uack'  # Single quotes preferred for a static string
wget "http://example.com/q&uack"  # Double quotes work here, too (no $ or ` in the value)
wget http://example.com/q\&uack   # Backslash escape
wget http://example.com/q'&'uack  # Only the metacharacter really needs quoting

Das letzte Beispiel schlägt auch ein anderes nützliches Konzept vor, das ich gerne als "Wippzitat" bezeichne. Wenn Sie einfache und doppelte Anführungszeichen mischen müssen, können Sie sie nebeneinander verwenden. Zum Beispiel die folgenden Zeichenfolgen in Anführungszeichen

'$HOME '
"isn't"
' where `<3'
"' is."

kann hintereinander eingefügt werden und bildet nach dem Tokenisieren und Entfernen von Anführungszeichen eine einzige lange Zeichenfolge.

$ echo '$HOME '"isn't"' where `<3'"' is."
$HOME isn't where `<3' is.

Dies ist nicht schlecht lesbar, aber es ist eine übliche Technik und daher gut zu wissen.

Abgesehen davon sollten Skripte normalerweise lsfür nichts verwendet werden. Um einen Platzhalter zu erweitern, verwenden Sie ihn einfach.

$ printf '%s\n' $pattern   # not ``ls -1 $pattern''
file1.txt
file_other.txt

$ for file in $pattern; do  # definitely, definitely not ``for file in $(ls $pattern)''
>  printf 'Found file: %s\n' "$file"
> done
Found file: file1.txt
Found file: file_other.txt

(Die Schleife ist im letzteren Beispiel völlig überflüssig. Sie printffunktioniert auch mit mehreren Argumenten stateinwandfrei. Das Schleifen über eine Platzhalterübereinstimmung ist jedoch ein häufiges Problem und wird häufig falsch ausgeführt.)

Eine Variable, die eine Liste von Token enthält, die durchlaufen werden sollen, oder ein Platzhalter, der erweitert werden soll, wird seltener angezeigt. Daher wird manchmal die Abkürzung "Alles zitieren, wenn Sie nicht genau wissen, was Sie tun" abgekürzt.

Tripleee
quelle
1
Dies ist eine Variante (eines Teils) einer Antwort, die ich auf eine verwandte Frage gepostet habe . Ich füge es hier ein, weil dies prägnant und klar genug ist, um eine kanonische Frage für dieses spezielle Problem zu werden.
Tripleee
4
Ich werde bemerken, dass dies Punkt 0 und ein wiederkehrendes Thema in der mywiki.wooledge.org/BashPitfalls-Sammlung häufiger Bash-Fehler ist. Viele, viele der einzelnen Elemente auf dieser Liste befassen sich im Wesentlichen mit diesem Problem.
Tripleee
26

Hier ist eine Drei-Punkte-Formel für Anführungszeichen im Allgemeinen:

Anführungszeichen

In Kontexten, in denen wir das Teilen und Globbing von Wörtern unterdrücken möchten. Auch in Kontexten, in denen das Literal als Zeichenfolge und nicht als regulärer Ausdruck behandelt werden soll.

Einzelzitate

In String-Literalen, in denen die Interpolation und die spezielle Behandlung von Backslashes unterdrückt werden sollen. Mit anderen Worten, Situationen, in denen die Verwendung von doppelten Anführungszeichen unangemessen wäre.

Keine Zitate

In Kontexten, in denen wir absolut sicher sind, dass es keine Probleme beim Aufteilen oder Globbing von Wörtern gibt, oder wenn wir das Aufteilen und Globbing von Wörtern wünschen .


Beispiele

Anführungszeichen

  • Literalzeichenfolgen mit Leerzeichen ( "StackOverflow rocks!", "Steve's Apple")
  • variable Erweiterungen ( "$var", "${arr[@]}")
  • Befehlsersetzungen ( "$(ls)", "`ls`")
  • Globs, in denen der Verzeichnispfad oder der Dateinamenbereich Leerzeichen enthält ( "/my dir/"*)
  • um einfache Anführungszeichen zu schützen ( "single'quote'delimited'string")
  • Bash-Parametererweiterung ( "${filename##*/}")

Einzelzitate

  • Befehlsnamen und Argumente mit Leerzeichen
  • Literalzeichenfolgen, deren Interpolation unterdrückt werden muss ( 'Really costs $$!', 'just a backslash followed by a t: \t')
  • um doppelte Anführungszeichen zu schützen ( 'The "crux"')
  • Regex-Literale, deren Interpolation unterdrückt werden muss
  • Verwenden Sie Shell-Anführungszeichen für Literale mit Sonderzeichen ( $'\n\t')
  • Verwenden Sie Shell-Anführungszeichen, wenn Sie mehrere einfache und doppelte Anführungszeichen schützen müssen ( $'{"table": "users", "where": "first_name"=\'Steve\'}')

Keine Zitate

  • um Standard - numerische Variablen ( $$, $?, $#etc.)
  • in arithmetischen Kontexten wie ((count++)), "${arr[idx]}","${string:start:length}"
  • Insider- [[ ]]Ausdruck, der frei von Problemen bei der Wortaufteilung und beim Globbing ist (dies ist eine Frage des Stils und die Meinungen können stark variieren).
  • wo wir Wortaufteilung wollen ( for word in $words)
  • wo wir globbing wollen ( for txtfile in *.txt; do ...)
  • wo wir ~interpretiert werden wollen als $HOME( ~/"some dir"aber nicht "~/some dir")

Siehe auch:

Codeforester
quelle
3
Nach diesen Richtlinien würde man eine Liste der Dateien im Stammverzeichnis erhalten, indem man schreibt. "ls" "/" Der Ausdruck "alle Zeichenfolgenkontexte" muss sorgfältiger qualifiziert werden.
William Pursell
5
In [[ ]]ist das Zitieren auf der rechten Seite von =/ ==und wichtig =~: Es macht den Unterschied zwischen der Interpretation einer Zeichenfolge als Muster / Regex oder wörtlich.
Benjamin W.
6
Ein guter Überblick, aber die Kommentare von @ BenjaminW. sind eine Integration wert und ANSI C-zitierte Strings ( $'...') sollten definitiv einen eigenen Abschnitt haben.
mklement0
3
@ mklement0, in der Tat sind sie gleichwertig. Diese Richtlinien weisen darauf hin, dass Sie immer "ls" "/"anstelle der allgemeineren tippen sollten ls /, und ich betrachte dies als einen Hauptfehler in den Richtlinien.
William Pursell
4
Für keine Anführungszeichen können Sie Variablenzuweisung hinzufügen oder case:)
PesaThe
4

Ich benutze in der Regel zitiert wie "$var"für sicher, es sei denn, ich bin sicher, dass $vardas keinen Platz enthält.

Ich benutze $varals einfache Möglichkeit, Linien zu verbinden:

lines="`cat multi-lines-text-file.txt`"
echo "$lines"                             ## multiple lines
echo $lines                               ## all spaces (including newlines) are zapped
Bach Pfandrecht
quelle
Der letzte Kommentar ist etwas irreführend; Die Zeilenumbrüche werden effektiv durch Leerzeichen ersetzt und nicht einfach entfernt.
Tripleee
-1

Verwenden Sie für die Verwendung der Variablen im Shell-Skript "" in Anführungszeichen, da die Variable Leerzeichen oder Sonderzeichen enthält, die die Ausführung Ihres Shell-Skripts nicht beeinflussen. Andernfalls können Sie sie ohne "" verwenden, wenn Sie sicher sind, dass Ihr Variablenname keine Leerzeichen oder Sonderzeichen enthält.

Beispiel:

echo "$ url name" - (Kann jederzeit verwendet werden)

echo "$ url name" - (Kann in solchen Situationen nicht verwendet werden, seien Sie also vorsichtig, bevor Sie es verwenden)

Vipul Sharma
quelle