Dlaczego `|` nie jest traktowane dosłownie według wzorca globalnego?

13

Moje pytanie pochodzi z tego, w jaki sposób przechowywanie wyrażenia regularnego w zmiennej powłoki pozwala uniknąć problemów z cytowaniem znaków specjalnych dla powłoki? .

  1. Dlaczego występuje błąd:

    $ [[ $a = a|b ]]  
    bash: syntax error in conditional expression: unexpected token `|'
    bash: syntax error near `|b'

    Wewnątrz [[ ... ]]drugiego operandu =ma być wzór globowania.

    Czy a|bnie jest prawidłowym wzorcem globowania? Czy możesz wskazać, która reguła składniowa narusza tę zasadę?

  2. Niektóre komentarze poniżej wskazują, że |jest interpretowane jako potok.

    Następnie zmiana =na wzorzec globalny na wzorzec =~regex |działa

    $ [[ $a =~ a|b ]]

    Nauczyłem się z Learning Bash p180 w moim poprzednim poście, który |jest rozpoznawany jako potok na początku interpretacji, nawet przed innymi etapami interpretacji (w tym parsowaniem wyrażeń warunkowych w przykładach). Jak więc można |rozpoznać jako operator wyrażenia regularnego podczas używania =~, bez rozpoznawania go jako potoku podczas nieprawidłowego użycia, tak jak podczas używania =? To sprawia, że ​​myślę, że błąd składniowy w części 1 nie oznacza, że |jest interpretowany jako potok.

    Każdy wiersz odczytywany przez powłokę ze standardowego wejścia lub skryptu jest nazywany potokiem; zawiera jedno lub więcej poleceń oddzielonych od zera lub więcej znaków potoku (|). Dla każdego czytanego potoku powłoka dzieli go na polecenia, konfiguruje operacje wejścia / wyjścia dla potoku, a następnie wykonuje następujące czynności dla każdego polecenia (Rysunek 7-1):

Dzięki.

Tim
źródło
1
Zauważ, że w niektórych wersjach bash, parsowanie extglob (gdzie |jest specjalne) jest domyślnie włączone po prawej stronie [[ $var = $pattern ]]. Interesujące byłoby wyodrębnienie wersji i shoptkonfiguracji opcji, w których takie zachowanie jest widoczne - jeśli tylko te, w których extglobjest włączony, domyślnie lub jawnie, to dobrze.
Charles Duffy,
2
BTW, jeśli chcesz nieco bardziej kompleksowo wykluczyć przypadek, w którym postać potoku zakłóca wcześniejszy etap parsowania (co zgadzam się, że to się nie dzieje, ale nie jest tak oczywiste dla czytelnika, jak to możliwe), zrobiłbyś to użyj, pattern='a|b'a następnie rozwiń bez $patterncudzysłowu na RHS.
Charles Duffy,
@CharlesDuffy, o to właśnie chodziło w pytaniach i odpowiedziach, do których to pytanie jest kontynuacją.
Stéphane Chazelas
Ahh - kontekst ma sens; a twoja odpowiedź tutaj jest znakomita. Dziękuję z obu powodów.
Charles Duffy,
Tim, czy któraś z poniższych odpowiedzi odpowiada na twoje pytanie? Jeśli tak, prosimy o zaakceptowanie jednego. Dziękuję Ci!
Jeff Schaller

Odpowiedzi:

13

Nie ma żadnego dobrego powodu

[[ $a = a|b ]]

Powinien zgłosić błąd zamiast sprawdzać, czy $ a jest a|bciągiem, a [[ $a =~ a|b ]]nie zwraca błędu.

Jedynym powodem jest to, że |ogólnie (na zewnątrz i wewnątrz [[ ... ]]) jest znakiem specjalnym. W tej [[ $a =pozycji bashoczekuje typu tokena, który jest normalnym SŁOWEM, takim jak argumenty lub cele przekierowań w normalnym wierszu poleceń powłoki (ale tak, jakby extglobopcja była włączona od wersji bash 4.1).

( WORD tutaj odnoszę się do słowa w hipotetycznej gramatyce powłoki, takiej jak opisana w specyfikacji POSIX , to znaczy, że powłoka byłaby analizowana jako jeden token w prostej linii poleceń powłoki, a nie inna definicja słów takich jak angielski jedna z sekwencji liter lub ciąg znaków niespacyjnych. foo"bar baz", $(echo x y)są dwoma takimi WORD s).

W normalnym wierszu poleceń powłoki:

echo a|b

Jest echo aprzesyłany do b. a|bnie jest WORD , to trzy tokeny: a WORD , |token i token b WORD .

W przypadku zastosowania w [[ $a = a|b ]], bashoczekuje WORD , który nie może ( a), ale potem stwierdza nieoczekiwany |znak, który powoduje błąd.

Co ciekawe, bashnie narzeka na:

[[ $a = a||b ]]

Ponieważ jest to teraz atoken, po którym następuje ||token b, więc jest on przetwarzany w ten sam sposób jak:

[[ $a = a || b ]]

Który testuje że $ajest aalbo że bciąg jest niepusty.

Teraz w:

[[ $a =~ a|b ]]

bashnie może mieć tej samej reguły analizowania. Posiadanie tej samej reguły analizowania oznaczałoby, że powyższe spowodowałoby błąd i należałoby zacytować to, |aby upewnić się, że a|bjest to jedno SŁOWO . Ale od wersji Bash 3.2, jeśli to zrobisz:

[[ $a =~ 'a|b' ]]

To już nie pasuje do a|bwyrażenia regularnego, ale do a\|bwyrażenia regularnego. Oznacza to, że cytowanie powłoki ma efekt uboczny usuwania specjalnego znaczenia operatorów wyrażeń regularnych. Jest to cecha, więc zachowanie jest podobne do tego [[ $a = "?" ]], ale wzory wieloznaczne (używane w [[ $a = pattern ]]) są SŁOWAMI powłoki (na przykład używanymi w globach), a wyrażenia regularne nie.

Więc bashmusi traktować wszystkich rozszerzonych operatorów wyrażeń regularnych, które są normalnie inaczej znaki specjalne powłoki, takie jak |, (, )inaczej podczas analizowania argument =~operatora.

Pamiętaj jednak, że podczas gdy

 [[ $a =~ (ab)*c ]]

teraz działa

 [[ $a =~ [)}] ]]

nie. Potrzebujesz:

 [[ $a =~ [\)}] ]]
 [[ $a =~ [')'}] ]]

Który w poprzednich wersjach bashniepoprawnie pasowałby do odwrotnego ukośnika. Ten został naprawiony, ale

 [[ $a =~ [^]')'] ]]

Czy nie zgadza się na backslashem tak jak powinien na przykład. Bo bashnie zdaje sobie sprawy, że )jest w nawiasie, więc ucieka )się doprowadzić do [^]\)]regexp, który pasuje na dowolnym charakterze, ale ], \i ).

ksh93 ma znacznie gorsze błędy na tym froncie.

W zsh, jest to normalne shell słowo, które jest oczekiwane i cytowanie operatory regexp nie wpływa na znaczenie operatorów regexp.

[[ $a =~ 'a|b' ]]

a|bPasuje do wyrażenia regularnego.

Oznacza to, że =~można również dodać do polecenia [/ test:

[ "$a" '=~' 'a|b' ]
test "$a" '=~' 'a|b'

(również działają w yash. =~Należy podać w tym miejscu, zshponieważ =somethingjest tam specjalny operator powłoki).

bash 3.1 zachowywał się jak zsh. Zmieniło się w 3.2, prawdopodobnie, aby wyrównać ksh93(choć bashbyła to powłoka, która jako pierwsza wymyśliła [[ =~ ]]), ale nadal możesz zrobić BASH_COMPAT=31lub shopt -s compat31powrócić do poprzedniego zachowania (z wyjątkiem tego, że chociaż [[ $a =~ a|b ]]zwróci błąd w bash3.1, to już nie jest w bash -O compat31nowszych wersjach bash).

Mam nadzieję, że to wyjaśnia, dlaczego powiedziałem, że reguły są mylące i dlaczego używam:

[[ $a =~ $var ]]

pomaga w tym z przenośnością do innych powłok.

Stéphane Chazelas
źródło
zsh również zgłasza błąd [[ $a = a|b ]].
NotAnUnixNazi
@isaac, tak, o to mi chodzi. a|bNie jest to skorupa WORD tutaj, to jest a, |a bżeton. Podobnie echo a|bjak nie wypisuje a|bani nie rozwija a|bglobu, musisz to zacytować, |ponieważ jest to specjalny znak powłoki, który jest nieprawidłowy w tym kontekście. [[ $a = (a|b) ]]działałby tak echo (a|b), jak działałby jak (a|b)operator wieloznaczny Zsh.
Stéphane Chazelas
Sformułowanie i wyjaśnienie w odpowiedzi tylko na imię i nazwisko. To nie jest cała prawda.
NotAnUnixNazi
11

Standardowe globs ( "rozszerzania nazw") są: *, ?i [ ... ]. |nie jest poprawnym operatorem globalnym w standardowych ustawieniach (innych niż extglob).

Próbować:

shopt -s extglob
[[ a = @(a|b) ]] && echo matched
Jeff Schaller
źródło
1
Dzięki. Ale dlaczego nie jest |zinterpretowany dosłownie? Dlaczego występuje błąd składniowy?
Tim
1
To nie było cytowane.
Jeff Schaller
3
W standardowych ustawieniach |operator globalny nie jest, więc nie jest |interpretowany dosłownie bez cytowania? Dlaczego więc występuje błąd składniowy?
Tim
1
|jest postacią kontrolną; nigdy nie jest traktowany jako dosłowny znak w taki sam sposób, jak litera lub cyfra.
chepner
3
Ponieważ w tym trybie powłoka nie spodziewała się znaku przekierowania potoku w środku jeszcze nie zamkniętego [[]]. [[ $a = anie jest prawidłowym poleceniem, którego dane wyjściowe mogą być przesyłane do innego procesu (przynajmniej tak myślała powłoka, którą próbujesz wykonać).
Jason C
5

Jeśli chcesz dopasować wyrażenie regularne, test powinien wyglądać następująco:

[[ "$a" =~ a|b ]]
Uścisk śmierci
źródło
@Tim Powinieneś otwierać nowe pytania, a nie stale edytować bieżące pytanie.
ogrodnik
@gardenhead: Moja aktualizacja ma na celu wyjaśnienie moich pytań zamiast ich zmieniania, na wypadek gdybyś je przegapił. Drugą częścią, którą dodałem, jest pokazanie wyjaśnienia potoku jednego komentarza na temat mojego pierwotnego pytania (dlaczego błąd składniowy) jest nieprawidłowe.
Tim