@Kusalananda hat bereits das Grundproblem und seine Lösung erläutert , und auch der von @glenn jackmann verlinkte Bash-FAQ-Eintrag enthält viele nützliche Informationen. Hier ist eine detaillierte Erklärung, was in meinem Problem passiert, basierend auf diesen Ressourcen.
Wir werden ein kleines Skript verwenden, das jedes seiner Argumente in einer separaten Zeile ausgibt, um die Dinge zu veranschaulichen ( argtest.bash
):
#!/bin/bash
for var in "$@"
do
echo "$var"
done
Übergabeoptionen "manuell":
$ ./argtest.bash -rnv --exclude='.*'
-rnv
--exclude=.*
Wie erwartet werden die Teile -rnv
und --exclude='.*'
in zwei Argumente aufgeteilt, da sie durch ein Leerzeichen ohne Anführungszeichen voneinander getrennt sind (dies wird als Wortteilung bezeichnet ).
Beachten Sie auch, dass die Anführungszeichen .*
entfernt wurden: Die einzelnen Anführungszeichen weisen die Shell an, ihren Inhalt ohne spezielle Interpretation weiterzugeben , die Anführungszeichen selbst werden jedoch nicht an den Befehl weitergegeben .
Wenn wir jetzt die Optionen in einer Variablen als Zeichenfolge speichern (im Gegensatz zur Verwendung eines Arrays), werden die Anführungszeichen nicht entfernt :
$ OPTS="--exclude='.*'"
$ ./argtest.bash $OPTS
--exclude='.*'
Dies liegt an zwei Gründen: Die bei der Definition verwendeten doppelten Anführungszeichen $OPTS
verhindern eine Sonderbehandlung der einfachen Anführungszeichen, sodass letztere Teil des Werts sind:
$ echo $OPTS
--exclude='.*'
Wenn wir jetzt $OPTS
als Argument für einen Befehl verwenden , werden die Anführungszeichen vor der Parametererweiterung verarbeitet , sodass die Anführungszeichen $OPTS
"zu spät" auftreten.
Dies bedeutet, dass (in meinem ursprünglichen Problem) anstelle des Musters rsync
das Ausschlussmuster '.*'
(mit Anführungszeichen! ) Verwendet wird. .*
Dateien, deren Name mit einem einfachen Anführungszeichen gefolgt von einem Punkt beginnt und mit einem einfachen Anführungszeichen endet, werden ausgeschlossen. Offensichtlich war das nicht beabsichtigt.
Eine Problemumgehung wäre gewesen, die doppelten Anführungszeichen bei der Definition wegzulassen $OPTS
:
$ OPTS2=--exclude='.*'
$ ./argtest.bash $OPTS2
--exclude=.*
Es ist jedoch empfehlenswert, Variablenzuweisungen immer anzugeben, da in komplexeren Fällen geringfügige Unterschiede bestehen.
Wie @Kusalananda feststellte, .*
hätte auch ein Verzicht auf Zitate funktioniert. Ich hatte die Anführungszeichen hinzugefügt, um eine Mustererweiterung zu verhindern , aber das war in diesem speziellen Fall nicht unbedingt erforderlich :
$ ./argtest.bash --exclude=.*
--exclude=.*
Es stellt sich heraus, dass Bash zwar eine Mustererweiterung durchführt, das Muster --exclude=.*
jedoch keiner Datei entspricht, sodass das Muster an den Befehl weitergeleitet wird. Vergleichen Sie:
$ touch some_file
$ ./argtest.bash some_*
some_file
$ ./argtest.bash does_not_exit_*
does_not_exit_*
Es ist jedoch gefährlich, das Muster nicht in Anführungszeichen zu setzen, da das Muster erweitert wird, wenn (aus welchem Grund auch immer) eine Datei gefunden wurde, die übereinstimmt --exclude=.*
:
$ touch -- --exclude=.special-filenames-happen
$ ./argtest.bash --exclude=.*
--exclude=.special-filenames-happen
Lassen Sie uns abschließend sehen, warum die Verwendung eines Arrays mein Anführungszeichenproblem verhindert (zusätzlich zu den anderen Vorteilen der Verwendung von Arrays zum Speichern von Befehlsargumenten).
Bei der Definition des Arrays werden die Wörter wie erwartet aufgeteilt und die Anführungszeichen behandelt:
$ ARRAY_OPTS=( -rnv --exclude='.*' )
$ echo length of the array: "${#ARRAY_OPTS[@]}"
length of the array: 2
$ echo first element: "${ARRAY_OPTS[0]}"
first element: -rnv
$ echo second element: "${ARRAY_OPTS[1]}"
second element: --exclude=.*
Bei der Übergabe der Optionen an den Befehl verwenden wir die Syntax "${ARRAY[@]}"
, die jedes Element des Arrays zu einem separaten Wort erweitert:
$ ./argtest.bash "${ARRAY_OPTS[@]}"
-rnv
--exclude=.*