In einem Bash-Skript versuche ich, die von mir verwendeten Optionen rsync
in einer separaten Variablen zu speichern . Dies funktioniert gut für einfache Optionen (wie --recursive
), aber ich habe Probleme mit --exclude='.*'
:
$ find source
source
source/.bar
source/foo
$ rsync -rnv --exclude='.*' source/ dest
sending incremental file list
foo
sent 57 bytes received 19 bytes 152.00 bytes/sec
total size is 0 speedup is 0.00 (DRY RUN)
$ RSYNC_OPTIONS="-rnv --exclude='.*'"
$ rsync $RSYNC_OPTIONS source/ dest
sending incremental file list
.bar
foo
sent 78 bytes received 22 bytes 200.00 bytes/sec
total size is 0 speedup is 0.00 (DRY RUN)
Wie Sie sehen, funktioniert die Übergabe --exclude='.*'
an rsync
"manuell" einwandfrei ( .bar
wird nicht kopiert), wenn die Optionen zuerst in einer Variablen gespeichert werden.
Ich vermute, dass dies entweder mit den Anführungszeichen oder dem Platzhalter (oder beiden) zusammenhängt, aber ich konnte nicht herausfinden, was genau falsch ist.
Antworten:
Im Allgemeinen ist es eine schlechte Idee, eine Liste von separaten Elementen in eine einzelne Zeichenfolge herabzusetzen, unabhängig davon, ob es sich um eine Liste von Befehlszeilenoptionen oder um eine Liste von Pfadnamen handelt.
Verwenden Sie stattdessen ein Array:
oder
und später...
Auf diese Weise bleibt die Notierung der einzelnen Optionen erhalten (solange Sie die Erweiterung von doppelt zitieren
${rsync_options[@]}
). Darüber hinaus können Sie die einzelnen Einträge des Arrays vor dem Aufruf auf einfache Weise bearbeitenrsync
.In jeder POSIX-Shell kann man dazu die Liste der Positionsparameter verwenden:
Auch hier ist die doppelte Angabe der Erweiterung von von
$@
entscheidender Bedeutung.Tangential verbunden:
Das Problem ist, dass die einfachen Anführungszeichen des
--exclude
Optionswerts Teil dieses Werts werden , wenn Sie die beiden Optionssätze in eine Zeichenfolge einfügen. Daher,hätte funktioniert¹ ... aber es ist besser (wie in sicherer), ein Array oder die Positionsparameter mit einzeln zitierten Einträgen zu verwenden. Auf diese Weise können Sie bei Bedarf auch Dinge mit Leerzeichen verwenden und vermeiden, dass die Shell Dateinamen für die Optionen generiert (globbing).
¹ vorausgesetzt , dass
$IFS
nicht geändert wird und dass es keine Datei , deren Name beginnt mit--exclude=.
im aktuellen Verzeichnis, und dass dienullglob
oderfailglob
Shell - Optionen sind nicht festgelegt.quelle
@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
):Übergabeoptionen "manuell":
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 :
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: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
: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 :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: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=.*
: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:
Bei der Übergabe der Optionen an den Befehl verwenden wir die Syntax
"${ARRAY[@]}"
, die jedes Element des Arrays zu einem separaten Wort erweitert:quelle
Wenn wir Funktionen und Shell-Skripten schreiben, in denen Argumente zur Verarbeitung übergeben werden, werden die Argumente in numerisch benannten Variablen übergeben, z. B. $ 1, $ 2, $ 3
Zum Beispiel :
bash my_script.sh Hello 42 World
Im Inneren
my_script.sh
werden Befehle verwendet$1
, um auf Hello,$2
to42
und$3
for zu verweisenWorld
Die Variablenreferenz
$0
wird auf den Namen des aktuellen Skripts erweitert, zmy_script.sh
Spielen Sie nicht den gesamten Code mit Befehlen als Variablen ab.
Denken Sie daran :
1 Vermeiden Sie die Verwendung von Variablennamen mit Großbuchstaben in Skripten.
2 Verwenden Sie keine Anführungszeichen, verwenden Sie stattdessen $ (...), da dies die bessere Verschachtelung darstellt.
quelle