Ich habe darüber gelesen, dass ich Variablen in bash zitieren sollte, zB "$ foo" anstelle von $ foo. Beim Schreiben eines Skripts stieß ich jedoch auf einen Fall, in dem es ohne Anführungszeichen, jedoch nicht mit diesen funktioniert:
wget_options='--mirror --no-host-directories'
local_root="$1" # ./testdir recieved from command line
remote_root="$2" # ftp://XXX recieved from command line
relative_path="$3" # /XXX received from command line
Dieser funktioniert:
wget $wget_options --directory_prefix="$local_root" "$remote_root$relative_path"
Dies ist nicht der Fall (beachten Sie die doppelten Anführungszeichen um $ wget_options):
wget "$wget_options" --directory_prefix="$local_root" "$remote_root$relative_path"
Was ist der Grund dafür?
Ist die erste Zeile die gute Version? oder sollte ich vermuten, dass irgendwo ein versteckter Fehler vorliegt, der dieses Verhalten verursacht?
Wo finde ich im Allgemeinen eine gute Dokumentation, um zu verstehen, wie Bash und seine Zitate funktionieren? Während ich dieses Skript schreibe, habe ich das Gefühl, dass ich angefangen habe, auf einer Trial-and-Error-Basis zu arbeiten, anstatt die Regeln zu verstehen.
wget
das nicht weiß, was--mirror --no-host-directories
bedeutet (als ein Argument), aber es behandelt es, wenn es in zwei Argumente aufgeteilt ist. Sehr wenige Programme behandeln Leerzeichen und Anführungszeichen besonders, wenn sie sich innerhalb des Argumentvektors befinden. Das Problem ist, dassbash
, und andere Muscheln, sollen>bash
Sie können sich also vorstellen, dass$a
dies dem direkten Schreiben des Inhalts entspricht. Jetzt ist das Problem offensichtlich:a="-a -b"; cmd "$a"
Erweitert sich zucmd "-a -b"
,cmd
weiß aber wahrscheinlich nicht, was das bedeutet.cmd $a
erweitert sichcmd -a -b
, was wahrscheinlich funktioniert .Antworten:
Grundsätzlich sollten Sie Variablenerweiterungen in doppelte Anführungszeichen setzen, um sie vor dem Teilen von Wörtern (und der Generierung von Dateinamen) zu schützen. In Ihrem Beispiel jedoch
Worttrennung ist genau das, was Sie wollen .
Mit
"$wget_options"
(zitiert),wget
weiß nicht, was mit dem einzelnen Argument zu tun ist--mirror --no-host-directories
und beschwert sichDamit
wget
die beiden Optionen angezeigt--mirror
und--no-host-directories
getrennt werden können, muss eine Wortteilung erfolgen.Es gibt robustere Möglichkeiten, dies zu tun. Wenn Sie
bash
eine andere Shell verwenden, die Arrays wiebash
do verwendet, lesen Sie die Antwort von Glenn Jackman . Die Antwort von Gilles beschreibt zusätzlich eine alternative Lösung für einfachere Schalen wie den Standard/bin/sh
. Beide speichern im Wesentlichen jede Option als separates Element in einem Array.Verwandte Frage mit guten Antworten: Warum verschluckt sich mein Shell-Skript an Leerzeichen oder anderen Sonderzeichen?
Variable Erweiterungen in doppelten Anführungszeichen sind eine gute Faustregel. Mach das . Dann sei dir der wenigen Fälle bewusst, in denen du das nicht tun solltest. Diese werden Ihnen anhand von Diagnosemeldungen wie der obigen Fehlermeldung angezeigt.
Darüber hinaus gibt es einige Fälle , in denen Sie nicht brauchen variable Erweiterungen zu zitieren. Es ist jedoch einfacher, weiterhin doppelte Anführungszeichen zu verwenden, da dies keinen großen Unterschied macht. Ein solcher Fall ist
Ein anderer ist
quelle
$IFS
den richtigen Wert enthält. Hier müssen Sie nach Leerzeichen aufteilen, und der Text enthält zufällig keine Tabulatoren oder Zeilenumbrüche. Der Standardwert von$IFS
würde dies jedoch tun, wenn dieser Code in einer Funktion verwendet werden soll, die möglicherweise in einem Kontext aufgerufen wird, in dem$IFS
Änderungen möglich gewesen wären , möchten Sie im$IFS
Voraus festlegen (und möglicherweise später wiederherstellen oder einen lokalen Bereich dafür verwenden, wenn der Rest des Codes eine unveränderte annimmt$IFS
)Die robusteste Methode zum Codieren ist die Verwendung eines Arrays:
quelle
cp
undrsync
werden unerwartete Dinge tun, wenn Ihr Befehl zu etwas erweitert wirdrsync '' rest of parameters
. Dies ist ideal, um einen Befehl Stück für Stück unter bestimmten Bedingungen zu erstellen und ihn dann nur einmal an einer Stelle auszuführen.Sie versuchen, eine Liste von Zeichenfolgen in einer Zeichenfolgenvariablen zu speichern. Es passt nicht. Egal wie Sie auf die Variable zugreifen, etwas ist kaputt.
wget_options='--mirror --no-host-directories'
Setzt die Variablewget_options
auf eine Zeichenfolge, die ein Leerzeichen enthält. An diesem Punkt ist es nicht möglich zu wissen, ob der Raum Teil einer Option oder ein Trennzeichen zwischen Optionen sein soll.Wenn Sie mit einer Ersetzung in Anführungszeichen auf die Variable zugreifen
wget "$wget_options"
, wird der Wert der Variablen als Zeichenfolge verwendet. Dies bedeutet, dass es als einzelner Parameter an übergeben wirdwget
, es ist also eine einzelne Option. Dies bricht in Ihrem Fall ab, weil Sie damit mehrere Optionen gemeint haben.Wenn Sie eine nicht in Anführungszeichen gesetzte Ersetzung verwenden
wget $wget_options
, wird der Wert der Zeichenfolgenvariablen einem Erweiterungsprozess mit dem Spitznamen "split + glob" unterzogen:$IFS
Variable nicht geändert ). Dies führt zu einer Zwischenliste von Zeichenfolgen.Dies funktioniert in Ihrem Beispiel, weil der Aufteilungsprozess das Leerzeichen in ein Trennzeichen verwandelt, aber im Allgemeinen nicht funktioniert, da eine Option Leerzeichen und Platzhalterzeichen enthalten kann.
In ksh, bash, yash und zsh können Sie eine Arrayvariable verwenden. Ein Array in der Shell-Terminologie ist eine Liste von Zeichenfolgen, sodass kein Informationsverlust auftritt. Um eine Array-Variable zu erstellen, setzen Sie Klammern um die Array-Elemente, wenn Sie der Variablen einen Wert zuweisen. Um auf alle Elemente des Arrays zuzugreifen, verwenden Sie - dies ist eine Verallgemeinerung von , die aus den Elementen des Arrays eine Liste bildet. Beachten Sie, dass Sie auch hier die doppelten Anführungszeichen benötigen, da ansonsten jedes Element split + glob durchläuft.
"${VARIABLE[@]}"
"$@"
In plain sh gibt es keine Arrayvariablen. Wenn es Ihnen nichts ausmacht, die Positionsargumente zu verlieren, können Sie sie zum Speichern einer Liste von Zeichenfolgen verwenden.
Weitere Informationen finden Sie unter Warum verschluckt sich mein Shell-Skript an Leerzeichen oder anderen Sonderzeichen?
quelle
(set -- ...; exec wget "$@" ...)
.