Jeśli od jakiegoś czasu obserwujesz unix.stackexchange.com, powinieneś wiedzieć, że pozostawienie zmiennej niecytowanej w kontekście listy (jak w echo $var
) w powłokach Bourne / POSIX (wyjątek to zsh) ma bardzo szczególne znaczenie i nie należy tego robić, chyba że masz ku temu dobry powód.
To omówiono obszernie w wielu Q & A tutaj (przykłady: ? Dlaczego mój skrypt powłoki zadławić spacji lub innych znaków specjalnych , kiedy to dwukrotnie powołując konieczne? , Ekspansja zmiennej powłoki i efekt glob i podzielona na nim , Cytowany vs niecytowane rozwinięcie łańcucha)
Tak było od pierwszego wydania powłoki Bourne'a pod koniec lat 70. i nie została zmieniona przez powłokę Korna (jedno z największych żalów Davida Korna (pytanie nr 7) ) lub bash
która w większości skopiowała powłokę Korna, i to jest jak to zostało określone przez POSIX / Unix.
Teraz nadal widzimy tutaj wiele odpowiedzi, a nawet czasami publicznie wydawany kod powłoki, w którym zmienne nie są cytowane. Można by pomyśleć, że ludzie już by się nauczyli.
Z mojego doświadczenia wynika, że są 3 typy ludzi, którzy pomijają cytowanie swoich zmiennych:
początkujący. Można to usprawiedliwić, ponieważ co prawda jest to całkowicie nieintuicyjna składnia. Naszą rolą na tej stronie jest ich edukowanie.
zapominalscy ludzie.
ludzie, którzy nie są przekonani nawet po wielokrotnym wbijaniu, którzy myślą, że z pewnością autor powłoki Bourne'a nie zamierzał cytować wszystkich naszych zmiennych .
Może uda nam się ich przekonać, jeśli ujawnimy ryzyko związane z tego rodzaju zachowaniami.
Co jest najgorsze niż może się zdarzyć, jeśli zapomnisz podać swoje zmienne. Czy to naprawdę takie złe?
O jakiej podatności tutaj mówimy?
W jakich kontekstach może to stanowić problem?
Odpowiedzi:
Preambuła
Po pierwsze, powiedziałbym, że nie jest to właściwy sposób rozwiązania problemu. To trochę jak powiedzenie „ nie powinieneś mordować ludzi, bo inaczej pójdziesz do więzienia ”.
Podobnie nie podajesz swojej zmiennej, ponieważ w przeciwnym razie wprowadzasz luki w zabezpieczeniach. Cytujesz swoje zmienne, ponieważ błędem jest nie (ale jeśli strach przed więzieniem może pomóc, dlaczego nie).
Małe podsumowanie dla tych, którzy właśnie wskoczyli do pociągu.
W większości powłok pozostawienie cudzysłowu zmiennego (choć to (i reszta tej odpowiedzi) dotyczy także podstawiania poleceń (
`...`
lub$(...)
) i rozszerzania arytmetycznego ($((...))
lub$[...]
)) ma bardzo szczególne znaczenie. Najlepszym sposobem na opisanie tego jest to, że przypomina to wywoływanie jakiegoś domyślnego operatora split + glob operator¹.w innym języku byłoby napisane coś takiego:
$var
jest najpierw dzielony na listę słów zgodnie ze złożonymi regułami obejmującymi$IFS
specjalny parametr ( część podzielona ), a następnie każde słowo powstałe w wyniku tego podziału jest uważane za wzorzec, który jest rozwijany do listy pasujących plików ( część globalna ) .Na przykład, jeśli
$var
zawiera*.txt,/var/*.xml
i$IFS
zawiera,
,cmd
zostanie wywołany z wieloma argumentami, z których pierwszycmd
totxt
pliki, a następne to pliki w bieżącym katalogu ixml
pliki w/var
.Jeśli chcesz zadzwonić
cmd
za pomocą dwóch dosłownych argumentówcmd
i*.txt,/var/*.xml
piszesz:który byłby w twoim innym, bardziej znanym języku:
Co rozumiemy przez podatność w powłoce ?
W końcu od samego początku wiadomo, że skryptów powłoki nie należy używać w kontekstach wrażliwych pod względem bezpieczeństwa. Z pewnością OK pozostawienie zmiennej bez cudzysłowu jest błędem, ale to nie może wyrządzić tyle szkody, prawda?
Cóż, pomimo faktu, że ktoś powiedziałby ci, że skrypty powłoki nigdy nie powinny być używane do internetowych interfejsów graficznych, lub że na szczęście większość systemów nie pozwala obecnie na skrypty powłoki setuid / setgid, jedną z rzeczy, które shellshock (zdalnie wykorzystywany błąd bash, który spowodował nagłówki z września 2014 r.) ujawniły, że powłoki są nadal szeroko stosowane tam, gdzie prawdopodobnie nie powinny: w CGI, w skryptach przechwytujących klienta DHCP, w poleceniach sudoers, wywoływanych przez (jeśli nie jako ) polecenia setuid ...
Czasem nieświadomie. Na przykład
system('cmd $PATH_INFO')
w skrypciephp
/perl
/python
CGI wywołuje powłokę, aby zinterpretować ten wiersz poleceń (nie wspominając o tym, żecmd
sam może być skryptem powłoki, a jego autor nigdy nie spodziewał się, że zostanie wywołany z CGI).Masz lukę, gdy istnieje ścieżka do eskalacji uprawnień, to znaczy, gdy ktoś (nazwijmy go atakującym ) jest w stanie zrobić coś, do czego nie jest przeznaczony.
Niezmiennie oznacza to, że atakujący dostarcza dane, które są przetwarzane przez uprzywilejowanego użytkownika / proces, który przypadkowo robi coś, czego nie powinien robić, w większości przypadków z powodu błędu.
Zasadniczo masz problem, gdy Twój błędny kod przetwarza dane pod kontrolą atakującego .
Teraz nie zawsze jest oczywiste, skąd pochodzą te dane , i często trudno jest stwierdzić, czy Twój kod kiedykolwiek przetworzy niezaufane dane.
Jeśli chodzi o zmienne, w przypadku skryptu CGI jest dość oczywiste, że dane to parametry GET / POST CGI i parametry takie jak pliki cookie, ścieżka, parametry hosta ...
W przypadku skryptu setuid (uruchamianego jako jeden użytkownik, gdy jest wywoływany przez innego), są to argumenty lub zmienne środowiskowe.
Innym bardzo częstym wektorem są nazwy plików. Jeśli otrzymujesz listę plików z katalogu, możliwe, że atakujący umieścił tam pliki .
W tym względzie, nawet po zachęcie interaktywnej powłoki, możesz być narażony (np. Podczas przetwarzania plików w
/tmp
lub~/tmp
na przykład).Nawet a
~/.bashrc
może być podatny na atak (na przykładbash
zinterpretuje go, gdy zostanie wywołany,ssh
aby uruchomićForcedCommand
podobnie jak wegit
wdrożeniach serwera z niektórymi zmiennymi pod kontrolą klienta).Teraz skrypt nie może być wywoływany bezpośrednio w celu przetwarzania niezaufanych danych, ale może być wywoływany przez inne polecenie, które to robi. Albo twój niepoprawny kod może zostać wklejony do skryptów, które to robią (przez ciebie 3 lata później lub jeden z twoich kolegów). Jednym z miejsc, w którym jest to szczególnie ważne, są odpowiedzi na stronach z pytaniami i odpowiedziami, ponieważ nigdy nie wiadomo, gdzie mogą znaleźć się kopie kodu.
W dół do biznesu; jak bardzo jest źle?
Pozostawienie cudzysłowu zmiennej (lub podstawienia polecenia) jest zdecydowanie najważniejszym źródłem luk w zabezpieczeniach związanych z kodem powłoki. Częściowo dlatego, że te błędy często przekładają się na luki w zabezpieczeniach, ale także dlatego, że tak często można zobaczyć niecytowane zmienne.
W rzeczywistości, szukając luk w kodzie powłoki, pierwszą rzeczą do zrobienia jest poszukiwanie niecytowanych zmiennych. Jest łatwa do wykrycia, często jest dobrym kandydatem, na ogół łatwa do prześledzenia do danych kontrolowanych przez osobę atakującą.
Istnieje nieskończona liczba sposobów, w jakie niecytowana zmienna może przekształcić się w podatność na atak. Podam tylko kilka wspólnych trendów.
Ujawnienie informacji
Większość ludzi wpadnie na błędy związane z niecytowanymi zmiennymi ze względu na podzieloną część (na przykład często pliki mają spacje w swoich nazwach, a spacja ma domyślną wartość IFS). Wiele osób przeoczy część globalną . Część glob jest co najmniej tak samo niebezpieczna jak część podzielona .
Globowanie wykonywane na nieautoryzowanych wejściach zewnętrznych oznacza, że osoba atakująca może zmusić Cię do przeczytania zawartości dowolnego katalogu.
W:
jeśli
$unsanitised_external_input
zawiera/*
, oznacza to, że atakujący może zobaczyć zawartość/
. Nie ma sprawy. Staje się bardziej interesująca choć/home/*
co daje listę nazw użytkowników na maszynie/tmp/*
,/home/*/.forward
na podpowiedzi w innych niebezpiecznych praktyk,/etc/rc*/*
dla służb obsługujących ... Nie ma potrzeby, aby wymienić je indywidualnie. Wartość/* /*/* /*/*/*...
spowoduje tylko wyświetlenie całego systemu plików.Luki w zabezpieczeniach typu „odmowa usługi”.
Biorąc poprzednią sprawę trochę za daleko i mamy DoS.
W rzeczywistości każda niecytowana zmienna w kontekście listy z niezaszyfrowanymi danymi wejściowymi stanowi przynajmniej lukę w zabezpieczeniach DoS.
Nawet eksperci od skryptów powłoki często zapominają cytować takie rzeczy jak:
:
jest poleceniem no-op. Co może pójść nie tak?Która jest przeznaczona do przypisania
$1
do$QUERYSTRING
jeśli$QUERYSTRING
był wyłączony. To szybki sposób na wywołanie skryptu CGI również z wiersza poleceń.Jest
$QUERYSTRING
to jednak nadal rozwinięte, a ponieważ nie jest cytowane, wywoływany jest operator split + glob .Obecnie istnieją globusy, których rozbudowa jest szczególnie droga. Ten
/*/*/*/*
jest wystarczająco zły, ponieważ oznacza wyświetlanie list katalogów do 4 poziomów w dół. Oprócz aktywności dysku i procesora oznacza to przechowywanie dziesiątek tysięcy ścieżek do plików (40 tys. Tutaj na minimalnej maszynie wirtualnej serwera, z czego 10 tys. Katalogów).Teraz
/*/*/*/*/../../../../*/*/*/*
oznacza 40k x 10k i/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*
wystarcza, aby rzucić nawet najpotężniejszą maszynę na kolana.Wypróbuj sam (choć przygotuj się na awarię lub zawieszenie komputera):
Oczywiście, jeśli kod to:
Następnie możesz wypełnić dysk.
Wystarczy zrobić wyszukiwania Google na CGI powłoki lub bash cgi lub ksh CGI , a znajdziesz kilka stron, które pokazują, jak napisać CGI w muszli. Zauważ, że połowa z tych, które przetwarzają parametry, jest wrażliwa.
Nawet własny David Korn jest podatny na atak (patrz obsługa plików cookie).
aż do luk w wykonywaniu dowolnego kodu
Wykonanie dowolnego kodu jest najgorszym rodzajem podatności, ponieważ jeśli atakujący może uruchomić dowolne polecenie, nie ma ograniczeń co do tego, co może zrobić.
Na ogół jest to podzielona część, która do nich prowadzi. Podział powoduje przekazanie kilku poleceń do poleceń, gdy tylko jeden jest oczekiwany. Podczas gdy pierwszy z nich zostanie użyty w oczekiwanym kontekście, pozostałe będą w innym kontekście, więc potencjalnie będą interpretowane inaczej. Lepiej z przykładem:
Tutaj celem było przypisanie zawartości
$external_input
zmiennej powłoki dofoo
awk
zmiennej.Teraz:
Drugie słowo wynikające z podziału
$external_input
nie jest przypisywane,foo
ale traktowane jakoawk
kod (tutaj, które wykonuje dowolne polecenie:)uname
.To przede wszystkim problem dla poleceń, które może wykonywać inne polecenia (
awk
,env
,sed
(GNU jeden)perl
,find
...), zwłaszcza z wariantów GNU (które akceptują opcje po argumentach). Zdarza się, że nie będzie podejrzewał polecenia, aby móc wykonać jak inniksh
,bash
lubzsh
„S[
lubprintf
...Jeśli utworzymy katalog o nazwie
x -o yes
, test staje się pozytywny, ponieważ oceniamy to wyrażenie warunkowe zupełnie inne.Co gorsza, jeśli utworzymy plik o nazwie
x -a a[0$(uname>&2)] -gt 1
, zawierający co najmniej wszystkie implementacje ksh (w tymsh
większość komercyjnych uniksów i niektóre BSD), który jest wykonywany,uname
ponieważ te powłoki wykonują obliczenia arytmetyczne na numerycznych operatorach porównania[
polecenia.To samo
bash
dotyczy nazwy pliku takiej jakx -a -v a[0$(uname>&2)]
.Oczywiście, jeśli nie mogą uzyskać arbitralnej egzekucji, atakujący może zadowolić się mniejszymi obrażeniami (co może pomóc w uzyskaniu arbitralnej egzekucji). Można użyć dowolnego polecenia, które może zapisywać pliki lub zmieniać uprawnienia, własność lub mieć jakikolwiek efekt główny lub uboczny.
Za pomocą nazw plików można wykonywać różne czynności.
I w końcu
..
zapisujesz (rekurencyjnie w GNUchmod
).Skrypty wykonujące automatyczne przetwarzanie plików w miejscach, w których można publicznie zapisywać,
/tmp
należy pisać bardzo ostrożnie.Co powiesz na
[ $# -gt 1 ]
Irytuje mnie to. Niektórzy ludzie zadają sobie trud zastanowienia się, czy dane rozszerzenie może być problematyczne, aby zdecydować, czy mogą pominąć cytaty.
To jak mówienie. Hej, wygląda na to, że
$#
nie może podlegać operatorowi split + glob, zapytajmy powłokę o split + glob . Albo hej, napiszmy niepoprawny kod tylko dlatego, że błąd prawdopodobnie nie zostanie trafiony .Teraz jak mało prawdopodobne? OK
$#
(lub$!
,$?
albo dowolny arytmetyka substytucja) może zawierać tylko cyfry (lub-
dla niektórych), więc glob część jest na zewnątrz. Aby część podzielona mogła coś zrobić, wszystko, czego potrzebujemy, to$IFS
zawierać cyfry (lub-
).Niektóre powłoki
$IFS
mogą być dziedziczone ze środowiska, ale jeśli środowisko nie jest bezpieczne, i tak gra się kończy.Teraz, jeśli napiszesz funkcję taką jak:
Oznacza to, że zachowanie twojej funkcji zależy od kontekstu, w którym jest ona wywoływana. Innymi słowy,
$IFS
staje się jednym z danych wejściowych. Ściśle mówiąc, kiedy piszesz dokumentację API dla swojej funkcji, powinna ona wyglądać mniej więcej tak:Kod wywołujący twoją funkcję musi się upewnić,
$IFS
że nie zawiera cyfr. Wszystko to dlatego, że nie miałeś ochoty pisać 2 podwójnych znaków cudzysłowu.Teraz, aby ten
[ $# -eq 2 ]
błąd mógł stać się podatny na atak, musisz w jakiś sposób uzyskać$IFS
kontrolę nad atakującym . Możliwe, że normalnie tak by się nie stało, gdyby atakujący nie wykorzystał innego błędu.Nie jest to jednak niespotykane. Częstym przypadkiem jest to, że ludzie zapominają o odkażaniu danych przed użyciem ich w wyrażeniach arytmetycznych. Widzieliśmy już powyżej, że może pozwolić na wykonanie dowolnego kodu w niektórych powłokach, ale we wszystkich pozwala atakującemu na podanie dowolnej zmiennej wartości całkowitej.
Na przykład:
A przy
$1
wartościach(IFS=-1234567890)
ta ocena arytmetyczna ma efekt uboczny ustawień IFS, a następna[
komenda kończy się niepowodzeniem, co oznacza, że sprawdzanie zbyt wielu argumentów jest pomijane.Co się stanie, gdy operator split + glob nie zostanie wywołany?
Jest inny przypadek, w którym potrzebne są cudzysłowy wokół zmiennych i innych rozszerzeń: gdy jest on używany jako wzorzec.
nie sprawdzaj, czy
$a
i$b
są takie same (oprócz zzsh
), ale czy$a
pasuje do wzorca w$b
. I musisz zacytować,$b
jeśli chcesz porównać jako łańcuchy (to samo w"${a#$b}"
lub"${a%$b}"
lub"${a##*$b*}"
gdzie$b
należy je zacytować, jeśli nie należy tego traktować jako wzorzec).Oznacza to, że
[[ $a = $b ]]
może zwrócić wartość true w przypadkach, gdy$a
jest różna od$b
(na przykład, kiedy$a
jestanything
i$b
jest*
) lub może zwrócić false, gdy są identyczne (na przykład, gdy oba są$a
i$b
są[a]
).Czy może to stanowić lukę w zabezpieczeniach? Tak, jak każdy błąd. W tym miejscu atakujący może zmienić logiczny przepływ kodu skryptu i / lub złamać założenia, które przyjmuje twój skrypt. Na przykład z kodem takim jak:
Atakujący może ominąć czek, przekazując
'[a]' '[a]'
.Teraz, jeśli nie stosuje się ani dopasowania wzorca, ani operatora split + glob , jakie jest niebezpieczeństwo pozostawienia zmiennej bez cudzysłowu?
Muszę przyznać, że piszę:
Cytowanie nie szkodzi, ale nie jest absolutnie konieczne.
Jednak jednym z efektów ubocznych pomijania cytatów w tych przypadkach (na przykład w odpowiedziach na pytania i odpowiedzi) jest to, że może wysłać niewłaściwą wiadomość do początkujących:
że dobrze jest nie cytować zmiennych.Na przykład mogą zacząć myśleć, że jeśli
a=$b
jest OK, torównież będzie (co nie jest w wielu powłokach, ponieważ jest w argumentachexport a=$b
export
polecenia, więc w kontekście listy) lub.env a=$b
Co
zsh
?zsh
naprawiono większość tych niezręczności projektowych. Wzsh
(przynajmniej jeśli nie w sh / tryb emulacji ksh), jeśli chcesz, podział lub globbing lub pasujące do wzorca , musisz poprosić go wyraźnie:$=var
do podziału, a$~var
na glob lub za treść zmiennej należy traktować jako wzorzec.Jednak dzielenie (ale nie globowanie) wciąż odbywa się w sposób dorozumiany po niecytowanym zastąpieniu polecenia (jak w
echo $(cmd)
).Ponadto czasami niepożądanym efektem ubocznym nie cytowania zmiennej jest usunięcie pustki .
zsh
Zachowanie jest podobne do tego, co można osiągnąć w innych skorup wyłączając globbing całkowicie (zset -f
) i dzielenie (zIFS=''
). Jeszcze w:Nie będzie podziału + glob , ale jeśli
$var
jest pusty, zamiast otrzymać jeden pusty argument,cmd
nie otrzyma żadnego argumentu.Może to powodować błędy (jak oczywiste
[ -n $var ]
). Może to zepsuć oczekiwania i założenia skryptu i spowodować luki w zabezpieczeniach, ale nie mogę teraz wymyślić niezbyt dalekiego przykładu).A co kiedy zrobić potrzebujemy podziału + glob operatora?
Tak, zwykle wtedy, gdy chcesz pozostawić zmienną bez cudzysłowu. Ale musisz upewnić się, że odpowiednio dostroiłeś operatorów split i glob przed użyciem. Jeśli chcesz tylko część podzieloną, a nie część globalną (co ma miejsce przez większość czasu), musisz wyłączyć globbing (
set -o noglob
/set -f
) i naprawić$IFS
. W przeciwnym razie spowodujesz również luki w zabezpieczeniach (jak wspomniany wyżej przykład CGI Davida Korna).Wniosek
Krótko mówiąc, pozostawienie cudzysłowu w powłoce zmiennej (lub podstawieniu polecenia lub interpretacji arytmetycznej) może być bardzo niebezpieczne, szczególnie, gdy jest wykonywane w niewłaściwych kontekstach, i bardzo trudno jest ustalić, które z tych niewłaściwych kontekstów.
To jeden z powodów, dla których uważa się to za złą praktykę .
Dzięki za przeczytanie do tej pory. Jeśli przejdzie ci przez głowę, nie martw się. Nie można oczekiwać, że wszyscy zrozumieją wszystkie konsekwencje pisania kodu w taki sposób, w jaki go piszą. Dlatego mamy zalecenia dotyczące dobrych praktyk , dzięki czemu można je stosować, niekoniecznie rozumiejąc dlaczego.
(a jeśli nie jest to jeszcze oczywiste, unikaj pisania poufnych kodów w powłokach).
I proszę podać swoje zmienne w odpowiedziach na tej stronie!
¹ W
ksh93
ipdksh
pochodnych interpretacja nawiasów jest również wykonywana, chyba że globbing jest wyłączony (w przypadkuksh93
wersji do ksh93u +, nawet gdybraceexpand
opcja jest wyłączona).źródło
[[
, należy podać tylko RHS porównań:if [[ $1 = "$2" ]]; then
[[ $* = "$var" ]]
to nie to samo,[[ "$*" = "$var" ]]
jakby pierwszym znakiem$IFS
nie było spacjabash
(a takżemksh
jeśli$IFS
jest puste, ale w takim przypadku nie jestem pewien, co$*
jest równe, czy powinienem zgłosić błąd?).foo='bar; rm *'
, nie, nie będzie, jednak wyświetli zawartość bieżącego katalogu, który może być uznany za ujawnienie informacji.print $foo
inksh93
(gdzieprint
jest zamiennik dlaecho
tych adresów niektóre z jego wad) ma jednak lukę w iniekcji kodu (na przykład zfoo='-f%.0d z[0$(uname>&2)]'
) (naprawdę potrzebujeszprint -r -- "$foo"
.echo "$foo"
jest nadal niepoprawny i nie można go naprawić (choć ogólnie mniej szkodliwy)).[Zainspirowany tą odpowiedzią autorstwa cas .]
Ale co gdyby …?
Nie mogę powiedzieć ; to zawiedzie, jeśli jest pusty ciąg.
grep "$ignorecase" other_grep_args
$ignorecase
Odpowiedź; reakcja; reagowanie; odzew; oddźwięk:
Jak omówiono w drugiej odpowiedzi, to nadal nie powiedzie się, jeśli
IFS
zawiera a-
lub ani
. JeśliIFS
upewniłeś się, że nie zawiera żadnych znaków w zmiennej (i jesteś pewien, że twoja zmienna nie zawiera żadnych znaków globalnych), prawdopodobnie jest to bezpieczne.Istnieje jednak sposób, który jest bezpieczniejszy (choć jest nieco brzydki i nieintuicyjny): użyj
${ignorecase:+"$ignorecase"}
. Ze specyfikacji języka poleceń powłoki POSIX , w części 2.6.2 Rozszerzanie parametrów ,Sztuką tutaj, taką jaka jest, jest to, że używamy
ignorecase
jakoparameter
i"$ignorecase"
jakoword
. To${ignorecase:+"$ignorecase"}
znaczyTo prowadzi nas tam, gdzie chcemy iść: jeśli zmienna jest ustawiona na pusty ciąg, zostanie „usunięta” (całe to zwinięte wyrażenie nic nie da - nawet pusty ciąg), a jeśli zmienna ma wartość inną niż -pustą wartość, otrzymamy tę wartość, podaną.
Ale co gdyby …?
Odpowiedź; reakcja; reagowanie; odzew; oddźwięk:
Możesz pomyśleć, że jest to przypadek użyciaNie! Oprzyj się pokusie, aby nawet pomyśleć o skorzystaniu zeval
.eval
tego miejsca.Ponownie, jeśli upewniłeś się, że
IFS
nie zawiera żadnych znaków w zmiennej (z wyjątkiem spacji, które chcesz uhonorować) i jesteś pewien, że twoja zmienna nie zawiera żadnych znaków globu, to powyższe jest prawdopodobnie bezpieczny.Ale jeśli używasz bash (lub ksh, zsh lub yash), istnieje bezpieczniejszy sposób: użyj tablicy:
Z bash (1) ,
"${criteria[@]}"
Rozwija się więc do (w powyższym przykładzie) zera, dwóch lub czterech elementówcriteria
tablicy, z których każdy jest cytowany. W szczególności, jeśli żaden z warunków s nie jest spełniony,criteria
tablica nie ma treści (zgodnie zcriteria=()
instrukcją) i"${criteria[@]}"
ocenia na nic (nawet niewygodny pusty ciąg).Staje się to szczególnie interesujące i skomplikowane, gdy masz do czynienia z wieloma słowami, z których niektóre są dynamicznymi (użytkownika) danymi wejściowymi, których nie znasz z góry i mogą zawierać spacje lub inne znaki specjalne. Rozważać:
Uwaga:
$fname
jest cytowana przy każdym użyciu. Działa to nawet wtedy, gdy użytkownik wprowadzi coś takiego jakfoo bar
lubfoo*
;"${criteria[@]}"
ocenia do-name "foo bar"
lub-name "foo*"
. (Pamiętaj, że każdy element tablicy jest cytowany).Tablice nie działają we wszystkich powłokach POSIX; tablice to ksh / bash / zsh / yash-ism. Z wyjątkiem… istnieje jedna tablica obsługiwana przez wszystkie powłoki: lista argumentów, alias
"$@"
. Jeśli skończysz z listą argumentów, z którą zostałeś wywołany (np. Skopiowałeś wszystkie „parametry pozycyjne” (argumenty) do zmiennych lub w inny sposób je przetworzyłeś), możesz użyć listy arg jako tablicy:"$@"
Konstrukt (co historycznie było pierwsze) ma taką samą semantykę jak - rozszerza każdy argument (czyli każdy element listy argumentów) do osobnego słowa, jakby były wpisywane ."${name[@]}"
"$1" "$2" "$3" …
Fragment specyfikacji języka poleceń powłoki POSIX , w 2.5.2 Parametry specjalne ,
Pełny tekst jest nieco tajemniczy; kluczową kwestią jest to, że określa, że
"$@"
ma generować zero pól, gdy nie ma parametrów pozycyjnych. Uwaga historyczna: kiedy po"$@"
raz pierwszy wprowadzono go w powłoce Bourne'a (poprzednik bash) w 1979 r., Miał błąd, który"$@"
został zastąpiony pojedynczym pustym łańcuchem, gdy nie było parametrów pozycyjnych; zobacz Co to${1+"$@"}
znaczy w skrypcie powłoki i czym się różni"$@"
? , The Traditional Bourne Shell Family , Co to${1+"$@"}
znaczy ... i gdzie jest to konieczne? i"$@"
kontra${1+"$@"}
.Tablice pomagają również w pierwszej sytuacji:
____________________
PS (csh)
Powinno to być oczywiste, ale z korzyścią dla osób, które są tutaj nowe: csh, tcsh itp., Nie są powłokami Bourne / POSIX. To cała inna rodzina. Koń w innym kolorze. Cała inna gra w piłkę. Inna rasa kota. Ptaki innego pióra. A szczególnie inna puszka robaków.
Niektóre z wypowiedzi na tej stronie dotyczą csh; takich jak: dobrze jest zacytować wszystkie zmienne, chyba że masz dobry powód, aby tego nie robić, i jesteś pewien, że wiesz, co robisz. Ale w csh każda zmienna jest tablicą - tak się składa, że prawie każda zmienna jest tablicą tylko jednego elementu i działa dość podobnie do zwykłej zmiennej powłoki w powłokach Bourne / POSIX. A składnia jest okropnie inna (i mam na myśli okropnie ). Nie powiemy więc nic więcej o powłokach z rodziny csh.
źródło
csh
, wolisz użyć$var:q
niż,"$var"
ponieważ ten ostatni nie działa dla zmiennych zawierających znaki nowego wiersza (lub jeśli chcesz cytować elementy tablicy indywidualnie, zamiast łączyć je spacjami w pojedynczy argument).bitrate="--bitrate 320"
działa${bitrate:+$bitrate}
ibitrate=--bitrate 128
nie działa,${bitrate:+"$bitrate"}
ponieważ psuje polecenie. Czy korzystanie${variable:+$variable}
z niego jest bezpieczne"
?$@
do czegoś innego) lub po prostu odmawiasz użycia tablicy z innych powodów, odsyłam do „Jak omówiono w drugiej odpowiedzi”. … (Ciąg dalszy)${variable:+$variable}
ze nie"
) nie powiedzie się, jeśli IFS zawiera-
,0
,2
,3
,a
,b
,e
,i
,r
lubt
.${bitrate:+$bitrate}
Byłem sceptycznie nastawiony do odpowiedzi Stéphane'a, jednak można nadużyć
$#
:lub $ ?:
Są to wymyślone przykłady, ale potencjał istnieje.
źródło