Verwenden Sie immer in doppelte Anführungszeichen Variablenersetzungen und Befehlsersetzungen: "$foo"
,"$(foo)"
Wenn Sie $foo
nicht in Anführungszeichen setzen, wird Ihr Skript bei Eingaben oder Parametern (oder Befehlsausgaben $(foo)
) mit Leerzeichen oder blockiert \[*?
.
Dort können Sie aufhören zu lesen. Na gut, hier noch ein paar mehr:
read
- Um die Eingabe zeilenweise mit dem read
eingebauten Code zu lesen , verwenden Siewhile IFS= read -r line; do …
Plain , umread
Backslashes und Whitespace speziell zu behandeln.
xargs
- Vermeidenxargs
. Wenn Sie verwenden müssen xargs
, machen Sie das xargs -0
. Statt find … | xargs
, bevorzugtfind … -exec …
.
xargs
behandelt Whitespace und die Zeichen \"'
speziell.
Diese Antwort gilt für Bourne / POSIX-Stil Schalen ( sh
, ash
, dash
, bash
, ksh
, mksh
, yash
...). Zsh-Benutzer sollten es überspringen und das Ende von lesen. Wann ist eine doppelte Anführungszeichen erforderlich? stattdessen. Wenn Sie alles genau wissen wollen, lesen Sie den Standard oder das Handbuch Ihrer Shell.
Beachten Sie, dass die folgenden Erläuterungen einige Näherungswerte enthalten (Aussagen, die in den meisten Fällen zutreffen, jedoch vom umgebenden Kontext oder von der Konfiguration beeinflusst werden können).
Warum muss ich schreiben "$foo"
? Was passiert ohne die Anführungszeichen?
$foo
bedeutet nicht "den Wert der Variablen nehmen foo
". Es bedeutet etwas viel komplexeres:
- Nehmen Sie zunächst den Wert der Variablen.
- Feldaufteilung: Behandeln Sie diesen Wert als eine durch Leerzeichen getrennte Liste von Feldern und erstellen Sie die resultierende Liste. Wenn beispielsweise die Variable enthält
foo * bar
das Ergebnis dieses Schritt dann die 3-Element - Liste foo
, *
, bar
.
- Generierung von Dateinamen: Behandeln Sie jedes Feld als Glob, dh als Platzhalter, und ersetzen Sie es durch die Liste der Dateinamen, die diesem Muster entsprechen. Wenn das Muster nicht mit Dateien übereinstimmt, bleibt es unverändert. In unserem Beispiel ergibt dies die Liste mit
foo
, gefolgt von der Liste der Dateien im aktuellen Verzeichnis und schließlich bar
. Wenn das aktuelle Verzeichnis leer ist, ist das Ergebnis foo
, *
, bar
.
Beachten Sie, dass das Ergebnis eine Liste von Zeichenfolgen ist. In der Shell-Syntax gibt es zwei Kontexte: Listenkontext und String-Kontext. Feldaufteilung und Dateinamengenerierung erfolgen nur im Listenkontext, dies ist jedoch die meiste Zeit der Fall. Doppelte Anführungszeichen begrenzen einen Zeichenfolgenkontext: Die gesamte Zeichenfolge mit doppelten Anführungszeichen ist eine einzelne Zeichenfolge, die nicht geteilt werden darf. (Ausnahme: "$@"
zum Erweitern der Liste der Positionsparameter, entspricht z. B. dem "$@"
Vorhandensein von "$1" "$2" "$3"
drei Positionsparametern. Siehe Was ist der Unterschied zwischen $ * und $ @? )
Gleiches gilt für die Befehlsersetzung mit $(foo)
oder mit `foo`
. Verwenden Sie auf keinen Fall `foo`
: Die Angebotsregeln sind seltsam und nicht portierbar, und alle modernen Shells unterstützen $(foo)
nur intuitive Angebotsregeln, die absolut gleichwertig sind.
Die Ausgabe der arithmetischen Substitution wird ebenfalls erweitert, dies ist jedoch normalerweise kein Problem, da sie nur nicht erweiterbare Zeichen enthält (vorausgesetzt, sie IFS
enthält keine Ziffern oder -
).
Siehe Wann sind doppelte Anführungszeichen erforderlich? Weitere Informationen zu den Fällen, in denen Sie die Anführungszeichen weglassen können.
Denken Sie daran, Variablen- und Befehlssubstitutionen immer in Anführungszeichen zu setzen, es sei denn, Sie wollen damit einverstanden sein. Seien Sie vorsichtig: Das Auslassen von Anführungszeichen kann nicht nur zu Fehlern, sondern auch zu Sicherheitslücken führen .
Wie verarbeite ich eine Liste von Dateinamen?
Wenn Sie myfiles="file1 file2"
mit Leerzeichen schreiben , um die Dateien zu trennen, funktioniert dies nicht mit Dateinamen, die Leerzeichen enthalten. Unix-Dateinamen können andere Zeichen als /
(das ist immer ein Verzeichnisseparator) und Null-Bytes enthalten (die Sie in Shellskripten mit den meisten Shells nicht verwenden können).
Gleiches Problem mit myfiles=*.txt; … process $myfiles
. Wenn Sie dies tun, myfiles
enthält die Variable die 5-stellige Zeichenfolge *.txt
, und wenn Sie schreiben $myfiles
, wird der Platzhalter erweitert. Dieses Beispiel wird tatsächlich funktionieren, bis Sie Ihr Skript ändern myfiles="$someprefix*.txt"; … process $myfiles
. Wenn auf eingestellt someprefix
ist final report
, funktioniert dies nicht.
Um eine Liste beliebiger Art (z. B. Dateinamen) zu verarbeiten, fügen Sie sie in ein Array ein. Dies erfordert mksh, ksh93, yash oder bash (oder zsh, das nicht alle diese Anführungszeichen enthält); Eine einfache POSIX-Shell (z. B. ash oder dash) hat keine Array-Variablen.
myfiles=("$someprefix"*.txt)
process "${myfiles[@]}"
Ksh88 verfügt über Array-Variablen mit einer anderen Zuweisungssyntax set -A myfiles "someprefix"*.txt
(siehe Zuweisungsvariable unter verschiedenen ksh-Umgebungen, wenn Sie die Portierbarkeit von ksh88 / bash benötigen). Bourne / POSIX-Shells haben ein einziges Array, das Array der Positionsparameter, mit "$@"
denen Sie festlegen, set
und das für eine Funktion lokal ist:
set -- "$someprefix"*.txt
process -- "$@"
Was ist mit Dateinamen, die mit beginnen -
?
Beachten Sie in diesem Zusammenhang, dass Dateinamen mit einem -
(Bindestrich / Minus) beginnen können, was von den meisten Befehlen als Kennzeichnung einer Option interpretiert wird. Wenn Sie einen Dateinamen haben, der mit einem variablen Teil beginnt, stellen Sie sicher, dass Sie diesen voranstellen --
, wie im obigen Snippet. Dies zeigt dem Befehl an, dass das Ende der Optionen erreicht ist. Danach ist alles ein Dateiname, auch wenn es mit beginnt -
.
Alternativ können Sie sicherstellen, dass Ihre Dateinamen mit einem anderen Zeichen als beginnen -
. Absolute Dateinamen beginnen mit /
und können ./
am Anfang von relativen Namen hinzugefügt werden . Das folgende Snippet verwandelt den Inhalt der Variablen f
in eine "sichere" Art und Weise, auf dieselbe Datei zu verweisen, von der garantiert wird, dass sie nicht anfängt -
.
case "$f" in -*) "f=./$f";; esac
Beachten Sie abschließend, dass einige Befehle auch nachträglich -
als Standardeingabe oder Standardausgabe interpretiert werden --
. Wenn Sie auf eine tatsächliche Datei mit dem Namen verweisen -
müssen oder ein solches Programm aufrufen und nicht von stdin lesen oder in stdout schreiben möchten, müssen Sie die oben beschriebenen Schritte ausführen -
. Siehe Was ist der Unterschied zwischen „du -sh *“ und „du -sh ./*“? zur weiteren Diskussion.
Wie speichere ich einen Befehl in einer Variablen?
"Befehl" kann drei Dinge bedeuten: einen Befehlsnamen (der Name als ausführbare Datei mit oder ohne vollständigen Pfad oder der Name einer Funktion, eines eingebauten oder eines Alias), einen Befehlsnamen mit Argumenten oder einen Teil des Shell-Codes. Dementsprechend gibt es verschiedene Möglichkeiten, sie in einer Variablen zu speichern.
Wenn Sie einen Befehlsnamen haben, speichern Sie ihn einfach und verwenden Sie die Variable wie gewohnt in doppelten Anführungszeichen.
command_path="$1"
…
"$command_path" --option --message="hello world"
Wenn Sie einen Befehl mit Argumenten haben, ist das Problem dasselbe wie bei einer Liste der oben genannten Dateinamen: Dies ist eine Liste von Zeichenfolgen, keine Zeichenfolge. Sie können die Argumente nicht einfach in eine einzelne Zeichenfolge mit Leerzeichen dazwischen einfügen, da Sie sonst den Unterschied zwischen Leerzeichen, die Teil von Argumenten sind, und Leerzeichen, die Argumente trennen, nicht erkennen können. Wenn Ihre Shell über Arrays verfügt, können Sie diese verwenden.
cmd=(/path/to/executable --option --message="hello world" --)
cmd=("${cmd[@]}" "$file1" "$file2")
"${cmd[@]}"
Was ist, wenn Sie eine Shell ohne Arrays verwenden? Sie können die Positionsparameter weiterhin verwenden, wenn Sie nichts dagegen haben, sie zu ändern.
set -- /path/to/executable --option --message="hello world" --
set -- "$@" "$file1" "$file2"
"$@"
Was ist, wenn Sie einen komplexen Shell-Befehl speichern müssen, z. B. mit Umleitungen, Pipes usw.? Oder wenn Sie die Positionsparameter nicht ändern möchten? Anschließend können Sie eine Zeichenfolge mit dem Befehl eval
erstellen und die integrierte Zeichenfolge verwenden.
code='/path/to/executable --option --message="hello world" -- /path/to/file1 | grep "interesting stuff"'
eval "$code"
Beachten Sie die geschachtelten Anführungszeichen in der Definition von code
: Die einfachen Anführungszeichen '…'
begrenzen ein Zeichenfolgenliteral, sodass der Wert der Variablen code
die Zeichenfolge ist /path/to/executable --option --message="hello world" -- /path/to/file1
. Das eval
eingebaute Kommando weist die Shell an, die als Argument übergebene Zeichenfolge so zu analysieren, als ob sie im Skript enthalten wäre. An diesem Punkt werden also die Anführungszeichen und die Pipe analysiert usw.
Verwenden eval
ist schwierig. Überlegen Sie genau, was wann analysiert wird. Insbesondere können Sie nicht einfach einen Dateinamen in den Code einfügen: Sie müssen ihn in Anführungszeichen setzen, genau wie in einer Quellcodedatei. Es gibt keinen direkten Weg, das zu tun. So etwas code="$code $filename"
bricht , wenn der Dateiname enthält eine beliebige Shell - Sonderzeichen (Leerzeichen, $
, ;
, |
, <
, >
, etc.). code="$code \"$filename\""
bricht immer noch auf "$\`
. Gerade code="$code '$filename'"
bricht, wenn der Dateiname a enthält '
. Es gibt zwei Lösungen.
Fügen Sie dem Dateinamen eine Anführungszeichen-Ebene hinzu. Der einfachste Weg, dies zu tun, ist das Hinzufügen von einfachen Anführungszeichen und das Ersetzen einzelner Anführungszeichen durch '\''
.
quoted_filename=$(printf %s. "$filename" | sed "s/'/'\\\\''/g")
code="$code '${quoted_filename%.}'"
Behalten Sie die Variablenerweiterung im Code bei, damit sie nachgeschlagen wird, wenn der Code ausgewertet wird, und nicht, wenn das Codefragment erstellt wird. Dies ist einfacher, funktioniert aber nur, wenn die Variable zum Zeitpunkt der Codeausführung immer noch denselben Wert aufweist, nicht z. B., wenn der Code in einer Schleife erstellt wird.
code="$code \"\$filename\""
Benötigen Sie wirklich eine Variable mit Code? Die natürlichste Art, einem Codeblock einen Namen zu geben, besteht darin, eine Funktion zu definieren.
Was ist read
los mit ?
Ohne -r
, read
erlaubt Fortsetzungszeilen - dies ist eine einzige logische Eingabezeile:
hello \
world
read
Teilt die Eingabezeile in Felder auf, die durch Zeichen in $IFS
(ohne -r
Backslash) getrennt sind. Wenn zum Beispiel der Eingabe eine Zeile mit drei Worten ist, dann read first second third
setzt first
auf das erste Wort der Eingabe, der second
auf das zweite Wort und third
mit dem dritten Wort. Wenn es mehr Wörter gibt, enthält die letzte Variable alles, was nach dem Setzen der vorhergehenden übrig bleibt. Führende und nachfolgende Leerzeichen werden abgeschnitten.
Das Setzen IFS
auf die leere Zeichenkette vermeidet jegliches Beschneiden. Siehe Warum wird `while IFS = read` so oft verwendet, anstatt` IFS =; während gelesen..`? für eine längere Erklärung.
Was ist los mit xargs
?
Das Eingabeformat xargs
besteht aus durch Leerzeichen getrennten Zeichenfolgen, die wahlweise in einfache oder doppelte Anführungszeichen gesetzt werden können. Kein Standardwerkzeug gibt dieses Format aus.
Die Eingabe in xargs -L1
oder xargs -l
ist fast eine Liste von Zeilen, aber nicht ganz - wenn am Ende einer Zeile ein Leerzeichen steht, ist die folgende Zeile eine Fortsetzungszeile.
Sie können verwenden, xargs -0
wo zutreffend (und wo verfügbar: GNU (Linux, Cygwin), BusyBox, BSD, OSX, aber es ist nicht in POSIX). Das ist sicher, da Null-Bytes in den meisten Daten, insbesondere in Dateinamen, nicht vorkommen können. Verwenden find … -print0
Sie zum Erstellen einer durch Nullen getrennten Liste von Dateinamen (oder find … -exec …
wie unten erläutert).
Wie verarbeite ich Dateien, die von gefunden wurden find
?
find … -exec some_command a_parameter another_parameter {} +
some_command
muss ein externer Befehl sein, es kann keine Shell-Funktion oder ein Alias sein. Wenn Sie eine Shell aufrufen müssen, um die Dateien zu verarbeiten, rufen Sie sie sh
explizit auf.
find … -exec sh -c '
for x do
… # process the file "$x"
done
' find-sh {} +
Ich habe noch eine andere Frage
Durchsuchen Sie das Anführungszeichen auf dieser Website oder die Shell oder das Shell-Skript . (Klicken Sie auf "Weitere Informationen ...", um einige allgemeine Tipps und eine handverlesene Liste häufig gestellter Fragen anzuzeigen.) Wenn Sie gesucht haben und keine Antwort finden, fragen Sie nach .
shellcheck
Ihnen helfen, die Qualität Ihrer Programme zu verbessern.