Sprawdź, czy ciąg pasuje do wyrażenia regularnego w skrypcie Bash

204

Jednym z argumentów, że mój skrypt otrzymuje to data w następującym formacie: yyyymmdd.

Chcę sprawdzić, czy otrzymam prawidłową datę jako dane wejściowe.

W jaki sposób mogę to zrobić? Próbuję użyć wyrażenia regularnego, takiego jak:[0-9]\{\8}

Peter Nijem
źródło
Sprawdzanie poprawności formatu jest łatwe. Ale nie sądzę, że można w bash (z wbudowanymi) sprawdzić, czy data jest poprawna.
RedX

Odpowiedzi:

316

Możesz użyć konstrukcji testowej [[ ]]wraz z operatorem dopasowania wyrażeń regularnych =~, aby sprawdzić, czy łańcuch pasuje do wzorca wyrażenia regularnego.

W konkretnym przypadku możesz napisać:

[[ $date =~ ^[0-9]{8}$ ]] && echo "yes"

Lub dokładniejszy test:

[[ $date =~ ^[0-9]{4}(0[1-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[0-1])$ ]] && echo "yes"
#           |^^^^^^^^ ^^^^^^ ^^^^^^  ^^^^^^ ^^^^^^^^^^ ^^^^^^ |
#           |   |     ^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^^^^^^ |
#           |   |          |                   |              |
#           |   |           \                  |              |
#           | --year--   --month--           --day--          |
#           |          either 01...09      either 01..09     end of line
# start of line            or 10,11,12         or 10..29
#                                              or 30, 31

Oznacza to, że możesz zdefiniować wyrażenie regularne w Bash pasujące do żądanego formatu. W ten sposób możesz:

[[ $date =~ ^regex$ ]] && echo "matched" || echo "did not match"

gdzie polecenia po &&są wykonywane, jeśli test się powiedzie, a polecenia po ||są wykonywane, jeśli test się nie powiedzie.

Zauważ, że jest to oparte na rozwiązaniu Aleksa-Daniela Jakimenko w weryfikacji formatu daty wejściowej użytkownika w bash .


W innych powłokach możesz użyć grep . Jeśli twoja powłoka jest zgodna z POSIX, zrób

(echo "$date" | grep -Eq  ^regex$) && echo "matched" || echo "did not match"

W przypadku ryb , które nie są zgodne z POSIX, możesz to zrobić

echo "$date" | grep -Eq "^regex\$"; and echo "matched"; or echo "did not match"
fedorqui „SO przestań szkodzić”
źródło
19
Jestem tego świadomy, ale lubię też brać pod uwagę, kto pyta i jak daleko jest do bashu. Jeśli zapewnimy bardzo skomplikowane warunki, nie nauczą się niczego i po prostu wrócą, gdy będą mieli inne wątpliwości. Wolę dać bardziej zrozumiałą odpowiedź.
fedorqui „SO przestań krzywdzić”
7
Heh Cóż, jedynym sposobem na naukę jest przeczytanie dużo dobrego kodu. Jeśli podajesz fałszywy kod, który jest łatwy do zrozumienia, ale nie jest zalecany do użycia - to zły sposób na naukę. Jestem też całkiem pewien, że dla tych, którzy dopiero zaczęli uczyć się bash (prawdopodobnie już znając niektóre bity innego języka), zrozumieją składnię bash dla wyrażeń regularnych łatwiej niż jakieś greppolecenia z -Eflagą.
Aleks-Daniel Jakimenko-A.
8
@ Aleks-DanielJakimenko Ponownie przejrzałem ten post i teraz zgadzam się, że najlepiej jest użyć wyrażenia regularnego bash. Dziękujemy za wskazanie dobrego kierunku, zaktualizowana odpowiedź.
fedorqui „SO przestań szkodzić”
4
Głosowanie, które pozwala użyć go nieco poza pytaniem OP, na przykład sh.
Dereckson,
3
@ Aleks-DanielJakimenko używając grep wydaje się być najlepszym rozwiązaniem, jeśli używasz sh, fishlub innych mniej wyposażone muszle.
tomekwi
47

W wersji bash 3 możesz użyć operatora „= ~”:

if [[ "$date" =~ ^[0-9]{8}$ ]]; then
    echo "Valid date"
else
    echo "Invalid date"
fi

Odniesienie: http://tldp.org/LDP/abs/html/bashver3.html#REGEXMATCHREF

UWAGA: Cytowanie w operatorze dopasowywania w podwójnych nawiasach [[]] nie jest już konieczne od wersji Bash 3.2

aliasav
źródło
20
Nie powinieneś używać char ”przy regularnej ekspresji? Bo kiedy używam ekspresji, nie działa
Dawid Drozd
Ponadto ucieczka przed odwrotnym ukośnikiem {i} jest podobnie problematyczna.
kbulgrien
32

Dobrym sposobem sprawdzenia, czy ciąg znaków jest poprawną datą, jest użycie polecenia date:

if date -d "${DATE}" >/dev/null 2>&1
then
  # do what you need to do with your date
else
  echo "${DATE} incorrect date" >&2
  exit 1
fi

z komentarza: można użyć formatowania

if [ "2017-01-14" == $(date -d "2017-01-14" '+%Y-%m-%d') ] 
Django Janny
źródło
9
Zdecydowanie oceń swoją odpowiedź, ponieważ pozwala funkcji daty radzić sobie z datami, a nie podatnymi na błędy wyrażeniami regularnymi
Ali
Przydaje się to do sprawdzania szerokich opcji dat, ale czy musisz to sprawdzić? Na przykład, jeśli date -d 2017-11-14eto zrobię , zwróci wtorek 14 listopada 05:00:00 UTC 2017, ale to zepsułoby mój skrypt.
Josiah
1
Możesz użyć czegoś takiego: jeśli ["2017-01-14" == $ (data -d "2017-01-14" '+% Y-% m-% d')] Sprawdza, czy data jest poprawna i sprawdź, czy wynik jest taki sam, jak wprowadzone dane. Nawiasem mówiąc, bądź bardzo ostrożny ze zlokalizowanym formatem daty (na przykład Miesiąc-Dzień-Rok vs. Dzień-Miesiąc-Rok)
Django Janny
1
Może nie działać, w zależności od lokalizacji. Amerykańskie daty sformatowane przy użyciu MM-DD-RRRR nie będą działać nigdzie indziej na świecie, przy użyciu DD-MM-RRRR (Europa) lub RRRR-MM-DD (niektóre miejsca w Azji)
Paul
@Paul, co może nie działać? Jak napisano w komentarzu, można użyć opcji formatowania ...
Betlista
4

Chciałbym użyć expr matchzamiast =~:

expr match "$date" "[0-9]\{8\}" >/dev/null && echo yes

Jest to lepsze niż obecnie akceptowana odpowiedź na użycie, =~ponieważ =~dopasuje również puste ciągi, których IMHO nie powinno. Załóżmy, że badvarnie jest zdefiniowany, a następnie [[ "1234" =~ "$badvar" ]]; echo $?podaje (niepoprawnie) 0, a expr match "1234" "$badvar" >/dev/null ; echo $?daje poprawny wynik 1.

Musimy wykorzystać >/dev/nulldo ukrycia expr match„s wartości wyjściowej , która jest dopasowana liczba znaków lub 0 jeśli znaleziono żadnego meczu. Uwaga: jego wartość wyjściowa różni się od statusu wyjścia . Status wyjścia wynosi 0, jeśli znaleziono dopasowanie, lub 1 w innym przypadku.

Ogólnie rzecz biorąc, składnia dla expr:

expr match "$string" "$lead"

Lub:

expr "$string" : "$lead"

gdzie $leadjest wyrażenie regularne. To exit statusbędzie prawda (0), jeśli leadpasuje do wiodącego wycinka string(Czy istnieje na to nazwa?). Na przykład expr match "abcdefghi" "abc"wychodzi true, ale expr match "abcdefghi" "bcd"wychodzi false. (Podziękowania dla @Carlo Wood za zwrócenie na to uwagi.

Penghe Geng
źródło
7
=~nie pasuje do pustych ciągów, dopasowujesz ciąg do pustego wzorca w podanym przykładzie. Składnia jest string =~ pattern, a pusty wzorzec pasuje do wszystkiego.
bstpierre
2
To nie pasuje do podłańcucha, zwraca (na standardowe wyjście) liczbę pasujących znaków wiodących , a status wyjścia jest prawdziwy, jeśli dopasowano co najmniej 1 znak. Dlatego pusty łańcuch (który pasuje do 0 znaków) ma status wyjścia false. Na przykład expr match "abcdefghi" "^" && echo Matched || echo No match- i expr match "abcdefghi" "bcd" && echo Matched || echo No match- oba wracają "0\nNo match". Gdzie jako pasujące "a.*f"powróci "6\nMatched". Użycie „^” w twoim przykładzie jest zatem również niepotrzebne i już sugerowane.
Carlo Wood
@bstpierre: tutaj nie chodzi o to, czy można zracjonalizować zachowanie =~pasujących pustych ciągów. Chodzi o to, że takie zachowanie może być nieoczekiwane i powodować błędy. Napisałem tę odpowiedź specjalnie dlatego, że ją spaliłem.
Penghe Geng
@PengheGeng Nieoczekiwane zachowanie? Jeśli wzorzec nie ma definicji ani ograniczeń, to w rzeczywistości pasuje do czegokolwiek. Brak wzoru pasuje do wszystkiego. Pisanie solidnego kodu jest odpowiedzią, nie uzasadniającą złego wyjaśnienia.
Anthony Rutledge
@AnthonyRutledge „solidny kod” wymaga najlepszego wykorzystania dostępnych narzędzi, aby zapobiec przypadkowym błędom kodowania. W kodzie Shell, w którym pustą zmienną można łatwo i przypadkowo wprowadzić w dowolnym momencie za pomocą błędów ortograficznych, nie sądzę, aby zezwolenie na dopasowanie pustych zmiennych było solidną funkcją. Najwyraźniej autor GNU exprzgadza się ze mną.
Penghe Geng
0

Jeśli użycie wyrażenia regularnego może być pomocne w ustaleniu, czy sekwencja znaków daty jest poprawna, nie można go łatwo użyć do ustalenia, czy data jest poprawna. Poniższe przykłady przekażą wyrażenie regularne, ale wszystkie są niepoprawnymi datami: 20180231, 20190229, 20190431

Jeśli więc chcesz sprawdzić, czy ciąg daty (nazwijmy go datestr) ma poprawny format, najlepiej go przeanalizować datei poprosić dateo konwersję ciągu do właściwego formatu. Jeśli oba ciągi są identyczne, masz prawidłowy format i prawidłową datę.

if [[ "$datestr" == $(date -d "$datestr" "+%Y%m%d" 2>/dev/null) ]]; then
     echo "Valid date"
else
     echo "Invalid date"
fi
kvantour
źródło