Dopasowanie wyrażeń regularnych w instrukcji Bash if

86

Co tu zrobiłem źle?

Próba dopasowania dowolnego ciągu zawierającego spacje, małe litery, wielkie litery lub cyfry. Znaki specjalne też byłyby fajne, ale myślę, że wymaga to ucieczki przed niektórymi postaciami.

TEST="THIS is a TEST title with some numbers 12345 and special char *&^%$#"

if [[ "$TEST" =~ [^a-zA-Z0-9\ ] ]]; then BLAH; fi

To oczywiście sprawdza tylko górne, dolne, liczby i spacje. Ale to nie działa.

* AKTUALIZACJA *

Chyba powinienem był być bardziej szczegółowy. Oto rzeczywisty wiersz kodu.

if [[ "$TITLE" =~ [^a-zA-Z0-9\ ] ]]; then RETURN="FAIL" && ERROR="ERROR: Title can only contain upper and lowercase letters, numbers, and spaces!"; fi

* AKTUALIZACJA *

./anm.sh: line 265: syntax error in conditional expression
./anm.sh: line 265: syntax error near `&*#]'
./anm.sh: line 265: `  if [[ ! "$TITLE" =~ [a-zA-Z0-9 $%^\&*#] ]]; then RETURN="FAIL" && ERROR="ERROR: Title can only contain upper and lowercase letters, numbers, and spaces!"; return; fi'
Atomiklan
źródło
Której powłoki faktycznie używasz? / bin / sh? / bin / bash? / bin / csh?
Willem Van Onsem
8
Bezpieczniej jest umieścić wyrażenie regularne w zmiennej. re='...whatever...'; [[ $string =~ $re ]](bez cudzysłowów - to jeden z rzadkich przypadków, w których zepsują coś, co działałoby bez nich).
Charles Duffy,
3
Zamiast tego umieść pojedyncze cudzysłowy wokół zadania. Podwójne cudzysłowy nie chronią odpowiednio znaków specjalnych.
tripleee
Wiele dzięki Charles! Nadal jest w porządku, nie umieszczając go w zmiennej, ale NIE wolno go w ogóle cytować! Na przykład: [[ $var =~ .* ]]for match regex .*(cokolwiek). Myślę, że jeśli użyjesz cudzysłowów, same cytaty są uważane za część wyrażenia regularnego…
Stéphane
4
Podsumowanie gotcha Znalazłem: (1.) zapisz wzorzec w zmiennej używając pojedynczych cudzysłowów pattern='^hello[0-9]*$'(2.) w wyrażeniu z podwójnym kwadratem, jeśli potrzebujesz dopasowania wyrażenia regularnego, NIE cytuj wzorca, ponieważ cytowanie WYŁĄCZA dopasowanie do wzorca wyrażenia regularnego. (tj. wyrażenie [[ "$x" =~ $pattern ]]będzie pasować przy użyciu wyrażenia regularnego, a wyrażenie [[ "$x" =~ "$pattern" ]]wyłącza dopasowywanie wyrażeń regularnych i jest równoważne[[ "$x" == "$pattern" ]] ).
Trevor Boyd Smith

Odpowiedzi:

177

Jest kilka ważnych rzeczy, które należy wiedzieć o [[ ]]konstrukcji basha . Pierwszy:

Dzielenie na słowa i rozwijanie nazw plików nie są wykonywane na słowach pomiędzy [[i ]]; wykonywane są interpretacja tyldy, interpretacja parametrów i zmiennych, interpretacja wyrażeń arytmetycznych, podstawianie poleceń, podstawianie procesów i usuwanie cytatów.

Druga sprawa:

Dostępny jest dodatkowy operator binarny „= ~”, ... ciąg po prawej stronie operatora jest traktowany jako rozszerzone wyrażenie regularne i odpowiednio dopasowywany ... Dowolna część wzorca może być cytowana w cudzysłowie, aby wymusić dopasowanie jako ciąg .

W związku z tym $vpo obu stronach =~zostanie rozwinięty do wartości tej zmiennej, ale wynik nie zostanie podzielony na słowa ani rozszerzony według nazwy ścieżki. Innymi słowy, pozostawienie rozszerzeń zmiennych bez cudzysłowów po lewej stronie jest całkowicie bezpieczne, ale trzeba wiedzieć, że rozszerzenia zmiennych będą występować po prawej stronie.

Więc jeśli piszesz: [[ $x =~ [$0-9a-zA-Z] ]]The $0wewnątrz regex po prawej stronie zostanie poszerzona przed regex jest interpretowany, co prawdopodobnie spowoduje regex niepowodzenie kompilacji (chyba ekspansji $0kończy się cyfrą lub interpunkcyjny symbolu, którego wartość ASCII jest mniejsza niż cyfra). Jeśli w ten sposób zacytujesz prawą stronę [[ $x =~ "[$0-9a-zA-Z]" ]], to prawa strona będzie traktowana jako zwykły ciąg, a nie wyrażenie regularne (i $0nadal będzie rozszerzana). To, czego naprawdę chcesz w tym przypadku, to[[ $x =~ [\$0-9a-zA-Z] ]]

Podobnie wyrażenie między [[i ]]jest dzielone na słowa przed interpretacją wyrażenia regularnego. Dlatego spacje w wyrażeniu regularnym muszą zostać zmienione lub cytowane. Jeśli chcesz, aby dopasować litery, cyfry lub spacje można użyć: [[ $x =~ [0-9a-zA-Z\ ] ]]. Inne znaki podobnie muszą być poprzedzone znakami ucieczki, na przykład #, które rozpoczynają komentarz, jeśli nie są cytowane. Oczywiście możesz umieścić wzorzec w zmiennej:

pat="[0-9a-zA-Z ]"
if [[ $x =~ $pat ]]; then ...

Wiele osób preferuje ten styl w przypadku wyrażeń regularnych zawierających wiele znaków, które musiałyby zostać usunięte lub zacytowane, aby przejść przez lekser basha. Ale uwaga: w tym przypadku nie możesz cytować rozwinięcia zmiennej:

# This doesn't work:
if [[ $x =~ "$pat" ]]; then ...

Na koniec myślę, że to, co próbujesz zrobić, to sprawdzić, czy zmienna zawiera tylko prawidłowe znaki. Najłatwiejszym sposobem sprawdzenia tego jest upewnienie się, że nie zawiera nieprawidłowego znaku. Innymi słowy, wyrażenie takie jak to:

valid='0-9a-zA-Z $%&#' # add almost whatever else you want to allow to the list
if [[ ! $x =~ [^$valid] ]]; then ...

!neguje test, zamieniając go na operator „nie pasuje”, a [^...]klasa znaku regex oznacza „dowolny znak inny niż ...”.

Połączenie interpretacji parametrów i operatorów regex może sprawić, że składnia wyrażeń regularnych bash będzie „prawie czytelna”, ale nadal istnieje kilka problemów. (Nie są tam zawsze?) Jednym z nich jest, że nie można umieścić ]w $valid, nawet jeśli $validbyły notowane, z wyjątkiem samego początku. (To jest reguła wyrażenia regularnego Posix: jeśli chcesz dołączyć ]do klasy postaci, musi zaczynać się od początku. -Może być na początku lub na końcu, więc jeśli potrzebujesz obu ]i -, musisz zacząć ]i zakończyć -, prowadząc do regex „wiem, co robię” emotikon: [][-])

rici
źródło
6
Chcę tylko zaznaczyć, że „! ~ To operator„ nie pasuje ” nie jest prawdą. Albo użyj if ! [[ $x =~ $y ]]alboif [[ ! $x =~ $y ]]
alkohol
shellchecker nie zgadza się ...SC2076: Don't quote rhs of =~, it'll match literally rather than as a regex.
Leonardo
4
@leonard: czym różni się to od mojego stwierdzenia „nie możesz cytować rozszerzenia zmiennej” i komentarza „To nie działa”? Co w tym jest niejasnego?
rici
1
@jinbeomhong: samo wyrażenie jest jak zwykle rozdzielane na słowa przy użyciu białych znaków. Ale rozszerzenia parametrów i poleceń nie są dzielone na słowa.
rici
1
@jinbeomhong: Nie mówię niczego innego niż podręcznik basha. „ słowa między znakami [[i ]]” są usuwane z tekstu programu, w ten sam sposób, w jaki wiersze poleceń są przetwarzane na słowa. Jednak w przeciwieństwie do wierszy poleceń, słowa nie są dzielone po rozwinięciu.
rici
26

Na wypadek, gdyby ktoś chciał mieć przykład ze zmiennymi ...

#!/bin/bash

# Only continue for 'develop' or 'release/*' branches
BRANCH_REGEX="^(develop$|release//*)"

if [[ $BRANCH =~ $BRANCH_REGEX ]];
then
    echo "BRANCH '$BRANCH' matches BRANCH_REGEX '$BRANCH_REGEX'"
else
    echo "BRANCH '$BRANCH' DOES NOT MATCH BRANCH_REGEX '$BRANCH_REGEX'"
fi
Oliver Pearmain
źródło
13

Wolałbym do tego użyć [:punct:]. Ponadto, a-zA-Z09-9może być tylko [:alnum:]:

[[ $TEST =~ ^[[:alnum:][:blank:][:punct:]]+$ ]]
konsolebox
źródło