Czytałem o tym, że powinienem zacytować zmienne w bash, np. „$ Foo” zamiast $ foo. Jednak podczas pisania skryptu znalazłem przypadek, w którym działa on bez cudzysłowów, ale nie z nimi:
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
Ten działa:
wget $wget_options --directory_prefix="$local_root" "$remote_root$relative_path"
Ten nie ma (zwróć uwagę na podwójne cudzysłowy wokół $ wget_options):
wget "$wget_options" --directory_prefix="$local_root" "$remote_root$relative_path"
Jaki jest tego powód?
Czy pierwsza linia to dobra wersja; czy powinienem podejrzewać, że gdzieś jest ukryty błąd, który powoduje takie zachowanie?
Ogólnie, gdzie znajdę dobrą dokumentację, aby zrozumieć, jak działa bash i jego cytowanie? Podczas pisania tego skryptu czuję, że zacząłem pracować na zasadzie prób i błędów zamiast rozumieć zasady.
wget
nie wie, co--mirror --no-host-directories
oznacza (jako jeden argument), ale obsługuje go, gdy jest podzielony na dwa argumenty. Bardzo niewiele programów specjalnie traktuje spacje i cudzysłowy, gdy znajdują się w wektorze argumentów. Problem polega na tymbash
, że i inne powłoki mają być>bash
, więc możesz sobie wyobrazić, że$a
jest to odpowiednik bezpośredniego pisania jej zawartości. Teraz problem jest oczywisty:a="-a -b"; cmd "$a"
rozwija sięcmd "-a -b"
, alecmd
prawdopodobnie nie wie, co to znaczy.cmd $a
rozszerza sięcmd -a -b
, co prawdopodobnie czyni pracę.Odpowiedzi:
Zasadniczo powinieneś podwójnie cytować rozszerzenia zmiennych, aby chronić je przed dzieleniem słów (i generowaniem nazw plików). Jednak w twoim przykładzie
dzielenie słów jest dokładnie tym, czego chcesz .
Z
"$wget_options"
(cytowany)wget
nie wie, co zrobić z pojedynczym argumentem--mirror --no-host-directories
i narzekaAby
wget
zobaczyć dwie opcje--mirror
i--no-host-directories
jako osobne, musi wystąpić podział słów.Istnieją bardziej niezawodne sposoby na zrobienie tego. Jeśli używasz
bash
lub jakiejkolwiek innej powłoki, która używa tablic takich jakbash
do, zobacz odpowiedź glenn jackman . Odpowiedź Gillesa dodatkowo opisuje alternatywne rozwiązanie dla bardziej płaskich pocisków, takie jak standard/bin/sh
. Oba zasadniczo przechowują każdą opcję jako osobny element w tablicy.Powiązane pytanie z dobrymi odpowiedziami: Dlaczego mój skrypt powłoki dusi się na białych znakach lub innych znakach specjalnych?
Dobrym pomysłem jest stosowanie podwójnego cytowania zmiennych. Zrób to . Zatem pamiętaj o niewielu przypadkach, w których nie powinieneś tego robić. Przedstawią się one za pośrednictwem komunikatów diagnostycznych, takich jak powyższy komunikat o błędzie.
Istnieje również kilka przypadków, w których nie trzeba cytować rozszerzeń zmiennych. Ale i tak łatwiej jest nadal używać podwójnych cudzysłowów, ponieważ nie robi to dużej różnicy. Jednym z takich przypadków jest
Innym jest
źródło
$IFS
zawiera on odpowiednią wartość. Tutaj musisz podzielić na spację, a tekst nie zawiera tabulatora ani nowego wiersza, więc domyślna wartość$IFS
by to zrobiła, ale jeśli ten kod ma być użyty w funkcji, która może być wywołana w kontekście, w którym$IFS
mógł zostać zmodyfikowany , chcesz$IFS
wcześniej ustawić (i ewentualnie przywrócić go później lub użyć lokalnego zasięgu, jeśli reszta kodu zakłada niezmodyfikowaną$IFS
)Najbardziej niezawodny sposób na użycie kodu:
źródło
cp
irsync
będą robić nieoczekiwane rzeczy, jeśli twoje polecenie rozwinie się do czegoś podobnegorsync '' rest of parameters
. Jest to świetne do warunkowego budowania polecenia kawałek po kawałku, a następnie po prostu uruchamianie go raz w jednym miejscu.Próbujesz zapisać listę ciągów w zmiennej łańcuchowej. To nie pasuje. Bez względu na sposób dostępu do zmiennej coś jest zepsute.
wget_options='--mirror --no-host-directories'
ustawia zmiennąwget_options
na ciąg znaków, który zawiera spację. W tym momencie nie ma sposobu, aby wiedzieć, czy przestrzeń ma być częścią opcji, czy też separatorem między opcjami.Gdy uzyskujesz dostęp do zmiennej z cytowanym podstawieniem
wget "$wget_options"
, wartość zmiennej jest używana jako ciąg. Oznacza to, że jest przekazywany jako pojedynczy parametr dowget
, więc jest to jedna opcja. To się psuje w twoim przypadku, ponieważ zamierzałeś oznaczać wiele opcji.Gdy używasz zastępowania bez cudzysłowu
wget $wget_options
, wartość zmiennej łańcuchowej podlega procesowi rozszerzenia o nazwie „split + glob”:$IFS
zmiennej). Daje to pośrednią listę ciągów.Dzieje się tak w twoim przykładzie, ponieważ proces podziału zamienia spację w separator, ale ogólnie nie działa, ponieważ opcja może zawierać spacje i znaki wieloznaczne.
W ksh, bash, yash i zsh możesz użyć zmiennej tablicowej. Tablica w terminologii powłoki to lista ciągów, więc nie ma utraty informacji. Aby utworzyć zmienną tablicową, podczas przypisywania wartości do zmiennej umieść nawiasy wokół elementów tablicy. Aby uzyskać dostęp do wszystkich elementów tablicy, użyj - jest to uogólnienie , które tworzy listę z elementów tablicy. Pamiętaj, że potrzebujesz tutaj podwójnych cudzysłowów, w przeciwnym razie każdy element zostanie podzielony na glob.
"${VARIABLE[@]}"
"$@"
W zwykłym sh nie ma zmiennych tablicowych. Jeśli nie masz nic przeciwko utracie argumentów pozycyjnych, możesz użyć ich do zapisania jednej listy ciągów.
Aby uzyskać więcej informacji, zobacz Dlaczego mój skrypt powłoki dusi się na białych znakach lub innych znakach specjalnych?
źródło
(set -- ...; exec wget "$@" ...)
.