W skrypcie Bash próbuję zapisać opcje, których używam, rsync
w osobnej zmiennej. Działa to dobrze w przypadku prostych opcji (np. --recursive
), Ale mam problemy z --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)
Jak widać, przekazywanie --exclude='.*'
do rsync
„ręcznie” działa dobrze ( .bar
nie jest kopiowane), nie działa, gdy opcje są najpierw przechowywane w zmiennej.
Zgaduję, że jest to albo związane z cudzysłowami, albo ze znakiem wieloznacznym (albo z obydwoma), ale nie byłem w stanie dowiedzieć się, co dokładnie jest nie tak.
Odpowiedzi:
Ogólnie rzecz biorąc, złym pomysłem jest obniżenie listy oddzielnych elementów w jednym ciągu, bez względu na to, czy jest to lista opcji wiersza poleceń, czy lista ścieżek.
Zamiast tego użyj tablicy:
lub
i później...
W ten sposób cytowanie poszczególnych opcji jest zachowane (pod warunkiem dwukrotnego zacytowania rozwinięcia
${rsync_options[@]}
). Umożliwia także łatwe manipulowanie poszczególnymi wpisami tablicy, gdybyś musiał to zrobić przed wywołaniemrsync
.W dowolnej powłoce POSIX można użyć do tego listy parametrów pozycyjnych:
Ponownie, podwójnie cytując rozszerzenie
$@
ma tutaj kluczowe znaczenie.Stycznie powiązane:
Problem polega na tym, że gdy umieścisz dwa zestawy opcji w ciągu, pojedyncze cudzysłowy wartości
--exclude
opcji stają się częścią tej wartości. W związku z tym,zadziałałoby¹ ... ale lepiej (jak bezpieczniej) użyć tablicy lub parametrów pozycyjnych z indywidualnie cytowanymi wpisami. Takie postępowanie pozwoliłoby również na użycie elementów ze spacjami, jeśli zajdzie taka potrzeba, i pozwala uniknąć generowania przez powłokę nazw plików (globbing) w opcjach.
¹ pod warunkiem, że
$IFS
nie jest zmodyfikowany i że--exclude=.
w bieżącym katalogu nie ma pliku, którego nazwa zaczyna się od, oraz że opcjenullglob
lubfailglob
powłoki nie są ustawione.źródło
@Kusalananda wyjaśnił już podstawowy problem i jak go rozwiązać, a także wpis FAQ Bash do którego link @glenn jackmann, również zawiera wiele przydatnych informacji. Oto szczegółowe wyjaśnienie tego, co dzieje się w moim problemie na podstawie tych zasobów.
Użyjemy małego skryptu, który drukuje każdy z argumentów w osobnym wierszu, aby zilustrować różne rzeczy (
argtest.bash
):Przekazywanie opcji „ręcznie”:
Zgodnie z oczekiwaniami części
-rnv
i--exclude='.*'
są podzielone na dwa argumenty, ponieważ są one oddzielone niecytowanymi białymi spacjami (nazywa się to dzieleniem słów ).Zauważ również, że cytaty wokół
.*
zostały usunięte: pojedyncze cytaty każą powłoce przekazać treść bez specjalnej interpretacji , ale same cytaty nie są przekazywane do polecenia .Jeśli teraz przechowujemy opcje w zmiennej jako ciąg (w przeciwieństwie do używania tablicy), wówczas cudzysłowy nie są usuwane :
Wynika to z dwóch powodów: podwójne cudzysłowy użyte podczas definiowania
$OPTS
zapobiegają specjalnemu traktowaniu pojedynczych cudzysłowów, więc te ostatnie są częścią wartości:Kiedy teraz używamy
$OPTS
jako argumentu dla polecenia, wówczas cudzysłowy są przetwarzane przed rozwinięciem parametru , więc cudzysłowy w$OPTS
pojawiają się „za późno”.Oznacza to, że (w moim pierwotnym problemie ) zamiast wzorca
rsync
używa wzorca wykluczenia'.*'
(z cudzysłowami!).*
- wyklucza pliki, których nazwa zaczyna się od pojedynczego cudzysłowu, po którym następuje kropka i kończy się pojedynczym cudzysłowiem. Oczywiście nie to było zamierzone.Obejściem tego problemu byłoby pominięcie podwójnych cudzysłowów podczas definiowania
$OPTS
:Jednak dobrą praktyką jest zawsze cytowanie przypisań zmiennych ze względu na subtelne różnice w bardziej złożonych przypadkach.
Jak zauważył @Kusalananda, nie cytowanie
.*
też by zadziałało. Dodałem cudzysłowy, aby zapobiec rozszerzaniu wzorców , ale w tym szczególnym przypadku nie było to absolutnie konieczne :Okazuje się, że Bash ma wykonać ekspansję wzoru, ale wzór
--exclude=.*
nie pasuje do żadnego pliku, więc wzór jest przekazywana do polecenia. Porównać:Nie cytowanie wzorca jest jednak niebezpieczne, ponieważ jeśli (z jakiegokolwiek powodu) istnieje plik pasujący,
--exclude=.*
wzorzec zostanie rozwinięty:Na koniec zobaczmy, dlaczego użycie tablicy zapobiega mojemu problemowi cytowania (oprócz innych zalet używania tablic do przechowywania argumentów poleceń).
Podczas definiowania tablicy podział słów i obsługa cytatów przebiega zgodnie z oczekiwaniami:
Podczas przekazywania opcji do polecenia używamy składni
"${ARRAY[@]}"
, która rozwija każdy element tablicy w osobne słowo:źródło
Kiedy piszemy funkcje i skrypty powłoki, w których przekazywane są argumenty do przetworzenia, argumenty będą przekazywane w zmiennych o nazwach numerycznych, np. 1 $, 2 $, 3 $
Na przykład :
bash my_script.sh Hello 42 World
Wewnątrz
my_script.sh
polecenia będą$1
się odnosić do Witaj,$2
do42
i$3
dlaWorld
Odwołanie do zmiennej
$0
rozwinie się do nazwy bieżącego skryptu, npmy_script.sh
Nie odtwarzaj całego kodu za pomocą poleceń jako zmiennych.
Pamiętaj :
1 Unikaj używania w skryptach nazw zmiennych pisanych wielkimi literami.
2 Nie używaj cudzysłowów, zamiast tego używaj $ (...), lepiej zagnieżdża się.
źródło