Różnica między [0–9], [[: cyfra:]] i \ d

35

W artykule Wikipedii na temat wyrażeń regularnych wydaje się, że [[:digit:]]= [0-9]= \d.

W jakich okolicznościach nie są one równe? Jaka jest różnica?

Po niektórych badaniach wydaje mi się, że jedną różnicą jest to, że wyrażenie w [:expr:]nawiasach zależy od ustawień regionalnych.

Harbinn
źródło
3
Czy artykuł w Wikipedii, który podałeś w celu odpowiedzi na twoje pytanie? Różne procesory / silniki wyrażeń regularnych obsługują różne składnie klas znaków (między innymi).
igal
@igal wiki mówi, że jest różnica, ale nie podaje zbyt wielu szczegółów. Pytam o szczegóły, coś w rodzaju isaaca, powiedział thrig. Jestem bardzo zainteresowany ich różnicą w grep, sed, awk ... czy to wersja GNU, czy nie.
harbinn

Odpowiedzi:

40

Tak, jest to [[:digit:]]~ [0-9]~ \d(gdzie ~ oznacza przybliżone).
W większości języków programowania (tam, gdzie jest obsługiwany) \d[[:digit:]](identyczny). Jest mniej powszechne niż (nie w POSIX ale jest w GNU ).
\d[[:digit:]]grep -P

W UNICODE jest wiele cyfr , na przykład:

123456789 # Hindu-Arabic cyfry arabskie
٠١٢٣٤٥٦٧٨٩ # ARABIC-INDIC
۰۱۲۳۴۵۶۷۸۹ # EXTENDED ARABIC-INDIC/PERSIAN
߀߁߂߃߄߅߆߇߈߉ # NKO DIGIT
०१२३४५६७८९ # DEVANAGARI

Z których wszystkie mogą być zawarte w [[:digit:]]lub \d.

Zamiast tego [0-9]generalnie są to tylko cyfry ASCII 0123456789.


Istnieje wiele języków: Perl, Java, Python, C. W których [[:digit:]](i \d) wymaga rozszerzenia znaczenia. Na przykład ten kod perla będzie pasował do wszystkich cyfr z góry:

$ a='0123456789 ٠١٢٣٤٥٦٧٨٩ ۰۱۲۳۴۵۶۷۸۹ ߀߁߂߃߄߅߆߇߈߉ ०१२३४५६७८९'

$ echo "$a" | perl -C -pe 's/[^\d]//g;' ; echo
0123456789٠١٢٣٤٥٦٧٨٩۰۱۲۳۴۵۶۷۸۹߀߁߂߃߄߅߆߇߈߉०१२३४५६७८९

Co jest równoważne z zaznaczeniem wszystkich znaków, które mają właściwości Unicode Numerici digits:

$ echo "$a" | perl -C -pe 's/[^\p{Nd}]//g;' ; echo
0123456789٠١٢٣٤٥٦٧٨٩۰۱۲۳۴۵۶۷۸۹߀߁߂߃߄߅߆߇߈߉०१२३४५६७८९

Który grep mógłby się reprodukować (konkretna wersja pcre może mieć inną wewnętrzną listę punktów kodu niż Perl):

$ echo "$a" | grep -oP '\p{Nd}+'
0123456789
٠١٢٣٤٥٦٧٨٩
۰۱۲۳۴۵۶۷۸۹
߀߁߂߃߄߅߆߇߈߉
०१२३४५६७८९

Zmień na [0-9], aby zobaczyć:

$ echo "$a" | grep -o '[0-9]\+'
0123456789

POSIX

Dla konkretnego POSIX BRE lub ERE:
Nie \djest obsługiwany (nie w POSIX, ale w GNU grep -P). [[:digit:]]jest wymagany przez POSIX, aby odpowiadał cyfrowej klasie znaków, która z kolei jest wymagana przez ISO C jako znaki od 0 do 9 i nic więcej. Tak tylko w C locale wszystko [0-9], [0123456789], \di [[:digit:]]znaczy dokładnie to samo. Nie [0123456789]ma żadnych możliwych błędnych interpretacji, [[:digit:]]jest dostępny w większej liczbie programów narzędziowych i zwykle oznacza tylko [0123456789]. \dJest obsługiwana przez kilku narzędzi.

Jeśli chodzi o [0-9]znaczenie wyrażeń zakresowych, POSIX definiuje je tylko w ustawieniach regionalnych C. w innych lokalizacjach może być inaczej (może to być kolejność kodów lub kolejność sortowania lub coś innego).

muszle

Niektóre implementacje mogą rozumieć zakres jako coś innego niż zwykły porządek ASCII (na przykład ksh93):

$ LC_ALL=en_US.utf8 ksh -c 'a="'"$a"'";echo "${a//[0-9]}"'
  ۹ ߀߁߂߃߄߅߆߇߈߉ ९

I to jest pewne źródło błędów, które czekają na wystąpienie.

Izaak
źródło
W praktyce w systemach POSIX iswctype()i symbolach BRE / ERE / w narzędziach POSIX, [0-9] i [[: digit:]] pasują tylko do 0123456789. I które będą wyraźnie w następnej wersji standardu
Stéphane Chazelas
Nie wiedziałem, że perljest \dw trybie Unicode dopasowanym cyframi dziesiętnymi z innych skryptów. Dziękuję za to. W PCRE patrz (*UCP)jak w GNU grep -Po '(*UCP)\d'lub grep -Po '(*UCP)[[:digit:]]aby klasy były oparte na właściwościach Unicode.
Stéphane Chazelas
Zgadzam się, że [:digit:]składnia sugeruje, że chcesz użyć lokalizacji, czyli tego, co użytkownik uważa za cyfrę. Nigdy nie używam, [:digit:]ponieważ w praktyce jest to to samo, co [0-9]w każdym przypadku, niezmiennie chcę dopasować na 0123456789, nigdy nie chcę dopasowywać na ٠١٢٣٤٥٦٧٨٩, i nie mogę wymyślić przypadku użycia, w którym ktoś chciałby dopasować cyfrę dziesiętną w dowolnym skrypcie z narzędziami POSIX. Zobacz także bieżącą dyskusję [:blank:]na temat zsh ML . Te klasy postaci są trochę bałaganu.
Stéphane Chazelas
13

Zależy to od sposobu zdefiniowania cyfry; [0-9]zwykle są to ASCII (lub być może coś innego, co nie jest ani ASCII ani nadzbiorem ASCII, ale ma takie same 10 cyfr jak w ASCII tylko z różnymi reprezentacjami bitów (EBCDIC)); \dz drugiej strony mogą to być zwykłe cyfry (stare wersje Perla lub nowoczesne wersje Perla z /awłączoną flagą wyrażeń regularnych) lub może to być dopasowanie Unicode, \p{Digit}które jest raczej większym zestawem cyfr niż [0-9]lub /\d/apasuje.

$ perl -E 'say "match" if 42 =~ m/\d/'
match
$ perl -E 'say "match" if "\N{U+09EA}" =~ m/\d/'
match
$ perl -E 'say "match" if "\N{U+09EA}" =~ m/\d/a'
$ perl -E 'say "match" if "\N{U+09EA}" =~ m/[0-9]/'
$ 

perldoc perlrecharclass aby uzyskać więcej informacji lub zajrzyj do dokumentacji danego języka, aby zobaczyć, jak się on zachowuje.

Ale czekaj, jest więcej! Ustawienia regionalne mogą również różnić się w zależności od tego \d, co pasuje, więc \dmoże pasować do mniejszej liczby cyfr niż pełny zestaw takich Unicode, i (mam nadzieję, że zwykle) także [0-9]. Jest to podobne do różnicy w C pomiędzy isdigit(3)( [0-9]) i isnumber(3)( [0-9plus cokolwiek innego z ustawień regionalnych).

Mogą istnieć połączenia, które można wykonać w celu uzyskania wartości cyfry, nawet jeśli nie jest to [0-9]:

$ perl -MUnicode::UCD=num -E 'say num(4)'
4
$ perl -MUnicode::UCD=num -E 'say num("\N{U+09EA}")'
4
$ 
gałązka
źródło
Myślę, że isnumber()to kwestia BSD, przynajmniej na podstawie strony
podręcznika
Mam coś z uprzedzeń BSD, tak
thrig
Flaga / a to specyficzny ogranicznik służący do zmniejszania listy cyfr Unicode w celu dopasowania tylko … modyfikator / a może być użyty do wymuszenia \ d dopasowania tylko ASCII od 0 do 9 . W związku z tym wymusza dopasowanie dokładnie tak samo i tylko [0-9].
Izaak
5

Inny sens [0-9], [[:digit:]]a \dprezentowane są w innych odpowiedzi. W tym miejscu chciałbym dodać różnice w implementacji silnika regex.

            [[:digit:]]    \d
grep -E               ✓     ×
grep -P               ✓     ✓
sed                   ✓     ×
sed -E                ✓     ×

Tak [[:digit:]]zawsze działa , \dzależy. W instrukcji grep na to wspomnieć, że [[:digit:]]to właśnie 0-9w tej Clokalizacji.

PS1: Jeśli wiesz więcej, rozwiń tabelę.

PS2: GNU grep 3.1 i GNU 4.4 jest używany do testu.

Harbinn
źródło
2
1) Istnieje wiele wersji grepi sed, z największą różnicą prawdopodobnie między wersjami GNU a innymi. Ta odpowiedź może być bardziej przydatna, jeśli wspomina o której wersji grepi do sedktórej się odnosi. Albo jakie jest źródło tej tabeli. 2) ta tabela może być również przepisana na tekst, ponieważ nie zawiera niczego, co wymagałoby, aby był to obraz
ilkkachu
@ilkkachu 1) do testu używany jest najnowszy GNU grep 3.1 i GNU 4.4. 2) Nie wiem jak stworzyć tabelę. Wygląda na to, że @ muru przekształcił tabelę w ładny tekst.
harbinn
@harbinn Edytuj to w swojej odpowiedzi.
Dan D.
@DanD. dodano informacje o wersji.
dziękuję
1
Zauważ, że wbudowany remoduł Pythona nie obsługuje [[: digit:]], ale biblioteka dodawania regexobsługuje go, więc bym trochę podrywał na zawsze działa. Zawsze działa w sytuacjach skarg posix.
Steve Barnes,
4

Różnice teoretyczne zostały już dość dobrze wyjaśnione w innych odpowiedziach, więc pozostaje wyjaśnienie różnic praktycznych .

Oto niektóre z najczęstszych przypadków użycia dla dopasowania cyfry:


Ekstrakcja danych jednym strzałem

Często, gdy chcesz rozbić niektóre liczby, same numery znajdują się w niezgrabnie sformatowanym pliku tekstowym. Chcesz je wyodrębnić do wykorzystania w twoim programie. Prawdopodobnie możesz określić format liczb (patrząc na plik) i swoje bieżące ustawienia narodowe, więc możesz użyć dowolnego formularza , o ile wykona on zadanie. \dwymaga najmniejszej liczby naciśnięć klawiszy, więc jest to bardzo często używane.

Wprowadzanie środków dezynfekujących

Masz pewne niezaufane dane wejściowe użytkownika (być może z formularza internetowego) i musisz upewnić się, że nie zawiera żadnych niespodzianek. Być może chcesz zapisać go w polu numerycznym w bazie danych lub użyć jako parametru polecenia powłoki do uruchomienia na serwerze. W tym przypadku naprawdę chcesz [0-9], ponieważ jest to najbardziej restrykcyjny i przewidywalny.

Walidacji danych

Masz trochę danych, których nie wykorzystasz do niczego „niebezpiecznego”, ale dobrze byłoby wiedzieć, czy jest to liczba. Na przykład twój program pozwala użytkownikowi na wprowadzenie adresu, a Ty chcesz zaznaczyć literówkę, jeśli dane wejściowe nie zawierają numeru domu. W takim przypadku prawdopodobnie chcesz być tak szeroki, jak to możliwe, tak też [[:digit:]]jest droga.


Wydaje się, że są to trzy najczęstsze przypadki użycia dopasowania cyfr. Jeśli uważasz, że przegapiłem ważny, proszę zostaw komentarz.

Gitara basowa
źródło
niezła robota, ma związek z problemami bezpieczeństwa, takimi jak ReDoS lub inne
fram