Błąd w teście nawiasu powłoki, gdy łańcuch jest lewym nawiasiem

27

Kiedyś byłem pewien, że cytowanie ciągów jest zawsze dobrą praktyką, aby uniknąć parsowania powłoki.

Potem natrafiłem na to:

$ x='('
$ [ "$x" = '1' -a "$y" = '1' ]
bash: [: `)' expected, found 1

Próbując wyizolować problem, otrzymujesz ten sam błąd:

$ [ '(' = '1' -a '1' = '1' ]
bash: [: `)' expected, found 1

Rozwiązałem problem w ten sposób:

[ "$x" = '1' ] && [ "$y" = '1' ]

Nadal muszę wiedzieć, co się tutaj dzieje.

Claudio
źródło
2
Aby obejść ten [[ "$x" = '1' && "$y" = '1' ]]
problem
3
Z tego powodu specyfikacja POSIX dla testu wyraźnie opisuje -ai -ojest przestarzała (co oznacza [OB]indeks górny obok ich specyfikacji). Jeśli [ "$x" = 1 ] && [ "$y" = 1 ]zamiast tego napisałeś , wszystko byłoby w porządku i dobrze by było w obrębie dobrze zdefiniowanego / znormalizowanego zachowania.
Charles Duffy,
6
To dlatego ludzie używali, [ "x$x" = "x1" ]aby zapobiegać błędnej interpretacji argumentów jako operatorów.
Jonathan Leffler,
@JathanathanLeffler: Hej, skondensowałeś moją odpowiedź na jedno zdanie, niesprawiedliwe! :) Jeśli ktoś używa powłoki POSIX jak dashBash, to nadal jest to przydatna praktyka.
Nominal Animal
Chcę podziękować wszystkim, którzy poświęcili czas na odpowiedź na moje pytanie, naprawdę docenili chłopaki :)! Chcę również podziękować za niezbędną edycję mojego pytania. Wejście na to forum czasami daje mi ten sam dreszczyk ucieczki z Alcatraz, zły ruch oznacza twoje życie. W każdym razie naprawdę chciałbym, aby moje podziękowania dotarły do ​​ciebie, zanim ten komentarz zostanie usunięty
Claudio,

Odpowiedzi:

25

Jest to bardzo niejasny przypadek narożny, w którym można rozważyć błąd w [definiowaniu wbudowanego testu ; jednak pasuje do zachowania rzeczywistego [pliku binarnego dostępnego w wielu systemach. O ile mogę powiedzieć, że dotyka tylko niektórych przypadkach i zmienną mającą wartość pasujący do [operatora jak (, !, =, -e, i tak dalej.

Pozwól mi wyjaśnić, dlaczego i jak obejść to w powłokach Bash i POSIX.


Wyjaśnienie:

Rozważ następujące:

x="("
[ "$x" = "(" ] && echo yes || echo no

Nie ma problemu; powyższe nie powoduje błędu, i wyniki yes. W ten sposób oczekujemy, że wszystko zadziała. Możesz zmienić ciąg porównania na, '1'jeśli chcesz, i wartość x, i będzie działać zgodnie z oczekiwaniami.

Zauważ, że rzeczywisty /usr/bin/[plik binarny zachowuje się w ten sam sposób. Jeśli uruchomisz np. Nie '/usr/bin/[' '(' = '(' ']'wystąpi błąd, ponieważ program może wykryć, że argumenty składają się z operacji porównywania jednego ciągu.

Błąd występuje, gdy my i z drugim wyrażeniem. Nie ma znaczenia, jakie jest drugie wyrażenie, o ile jest poprawne. Na przykład,

[ '1' = '1' ] && echo yes || echo no

wyprowadza yesi jest oczywiście prawidłowym wyrażeniem; ale jeśli połączymy te dwa,

[ "$x" = "(" -a '1' = '1' ] && echo yes || echo no

Bash odrzuca wyrażenie wtedy i tylko wtedy, gdy xjest (lub !.

Gdybyśmy mieli uruchomić powyższe za pomocą rzeczywistego [programu, tj

'/usr/bin/[' "$x" = "(" -a '1' = '1' ] && echo yes || echo no

błąd byłby zrozumiały: ponieważ powłoka dokonuje podstawień zmiennych, /usr/bin/[plik binarny odbiera tylko parametry ( = ( -a 1 = 1i zakończenie ], co zrozumiałe, nie analizuje, czy otwarte nawiasy rozpoczynają podwyrażenie, czy nie, z udziałem operacji i . Jasne, parsowanie go jako dwóch porównań ciągów jest możliwe, ale robienie tego zachłannie w ten sposób może powodować problemy po zastosowaniu do odpowiednich wyrażeń za pomocą nawiasów podwyrażeniowych.

Problem polega na tym, że [wbudowana powłoka zachowuje się w ten sam sposób, jakby zwiększała wartość xprzed sprawdzeniem wyrażenia.

(Te dwuznaczności i inne związane z rozszerzaniem zmiennych były głównym powodem, dla którego Bash zaimplementował i teraz zaleca używanie [[ ... ]]zamiast tego wyrażeń testowych).


Obejście jest trywialne i często występuje w skryptach używających starszych shpowłok. Często dodaje się „bezpieczny” znak xprzed ciągami (obie wartości są porównywane), aby zapewnić rozpoznanie wyrażenia jako porównania ciągów:

[ "x$x" = "x(" -a "x$y" = "x1" ]
Nominalne zwierzę
źródło
1
Nie nazwałbym zachowania [wbudowanego błędu błędem. Jeśli już, to nieodłączna wada projektowa. [[jest słowem kluczowym powłoki , a nie tylko wbudowanym poleceniem, więc przed usunięciem cudzysłowu może patrzeć na rzeczy i faktycznie zastępuje zwykłe dzielenie słów. np. [[ $x == 1 ]]nie trzeba używać "$x", ponieważ [[kontekst jest inny niż normalny. W każdym razie w ten sposób [[można uniknąć pułapek [. POSIX wymaga [takiego zachowania, jak bash, a bash jest w większości zgodny z POSIX, nawet bez niego --posix, więc zamiana [na słowo kluczowe jest nieatrakcyjna.
Peter Cordes,
Twoje obejście nie jest zalecane przez POSIX. Wystarczy użyć dwóch połączeń z [.
Wildcard,
@PeterCordes: Całkiem słusznie; Słuszna uwaga. (Być może powinienem był użyć zaprojektowanego zamiast zdefiniowanego w pierwszym akapicie, ale [jego zachowanie jest obciążone historią sprzed POSIX, zamiast tego wybrałem drugie słowo.) Osobiście unikam używania [[w moich przykładowych skryptach, ale tylko dlatego, że jest to wyjątek od rekomendacji cytowania, o której zawsze traktuję (ponieważ pomijanie cytatów jest najczęstszym powodem błędów skryptowych, jakie widzę), i nie pomyślałem o prostym akapicie wyjaśniającym, dlaczego [[jest wyjątkiem od reguł, bez podania mojej rekomendacji cytowania posądzać.
Nominal Animal
@Wildcard: Nie. POSIX nie zaleca przeciwdziałania tej praktyce i to jest ważne. Tylko dlatego, że władza nie poleca tej praktyki, nie czyni tego złym. Rzeczywiście, jak wskazałem, jest to praktyka historyczna stosowana w shskryptach, dobrze poprzedzająca standaryzację POSIX. Korzystanie nawiasach podwyrażeń i -ai -ooperatorów logicznych w testach / [jest bardziej efektywne, że opierając się na szeregowaniu ekspresji (przez &&a ||); po prostu na obecnych maszynach różnica nie ma znaczenia.
Nominal Animal
@Wildcard: Jednak osobiście wolę łączyć wyrażenia testowe za pomocą &&i ||, ale powodem jest to, że ludziom łatwiej jest je konserwować (czytać, rozumieć i modyfikować, jeśli to konieczne) bez wprowadzania błędów. Nie krytykuję twojej sugestii, a jedynie jej uzasadnienie. (W przypadku bardzo podobnych powodów .LT., .GE.itp operatory porównania w FORTRAN 77 dostaje dużo bardziej przyjazne dla człowieka wersje <, >=itd w późniejszych wersjach.)
Nominalna Animal
11

[aka testwidzi:

 argc: 1 2 3 4  5 6 7 8
 argv: ( = 1 -a 1 = 1 ]

testakceptuje podwyrażenia w nawiasach; uważa więc, że lewy nawias otwiera podwyrażenie i próbuje je przeanalizować; parser widzi =pierwszą rzecz w podwyrażeniu i myśli, że jest to domyślny test długości łańcucha, więc jest szczęśliwy; po podwyrażeniu powinien nastąpić prawy nawias, a zamiast tego parser znajdzie 1zamiast ). I narzeka.

Kiedy testma dokładnie trzy argumenty, a środkowy argument jest jednym z rozpoznawanych operatorów, stosuje ten operator do pierwszego i trzeciego argumentu bez szukania podwyrażeń w nawiasach.

Aby uzyskać szczegółowe informacje man bash, wyszukaj test expr.

Wniosek: stosowany algorytm analizowania testjest skomplikowany. Używaj tylko prostych wyrażeń i użyć powłoki operatorów !, &&a ||do ich łączenia.

AlexP
źródło