Jak grep dla grup n cyfr, ale nie więcej niż n?

33

Uczę się Linuksa i mam wyzwanie, którego sam nie potrafię rozwiązać. Oto on:

grep wiersz z pliku, który zawiera 4 liczby z rzędu, ale nie więcej niż 4.

Nie jestem pewien, jak do tego podejść. Mogę wyszukiwać określone liczby, ale nie ich liczbę w ciągu.

Budda
źródło
2
Czy wiersz powinien 1234a12345być wyświetlany, czy nie?
Eliah Kagan
@Buddha musisz wyjaśnić swoje pytanie wraz z przykładem.
Avinash Raj,
jeśli liczby są poprzedzone spacją lub początkiem zakotwiczenia linii, a następnie spacją lub końcem zakotwiczenia linii, możesz po prostu użyć granic słów. \b\d{4}\b
Avinash Raj,
1
To pytanie różni się od niektórych pytań dotyczących wyrażeń regularnych tym, że jawnie dotyczy użycia grep . Pytania dotyczące korzystania z narzędzi uniksowych w Ubuntu, takich jak grep, sed i awk, zawsze były tutaj dobrze oceniane. Czasami ludzie pytają, jak wykonać pracę z niewłaściwym narzędziem; brak kontekstu to duży problem, ale nie o to tu chodzi. Jest to temat, wystarczająco jasny, aby można było z niego skorzystać, pomocny dla naszej społeczności i nie ma korzyści w zapobieganiu dalszym odpowiedziom lub popychaniu ich do usunięcia lub migracji. Głosuję za ponownym otwarciem.
Eliah Kagan,
1
Dziękuję wam bardzo, nie miałem pojęcia, że ​​otrzymam tyle opinii. Oto odpowiedź, której szukałem: plik grep -E '(^ ​​| [^ 0-9]) [0-9] {4} ($ | [^ 0-9])'. Polecenie musi być w stanie wyciągnąć taki ciąg (co robi): abc1234abcd99999
Budda

Odpowiedzi:

52

Istnieją dwa sposoby interpretacji tego pytania; Zajmę się obydwoma przypadkami. Możesz chcieć wyświetlić linie:

  1. które zawierają ciąg czterech cyfr, który sam nie jest częścią dłuższej sekwencji cyfr, lub
  2. który zawiera czterocyfrową sekwencję, ale nie sekwencję cyfr (nawet osobno).

Na przykład wyświetli się (1) 1234a56789, ale (2) nie.


Jeśli chcesz wyświetlić wszystkie wiersze zawierające ciąg czterech cyfr, który sam nie jest częścią żadnej dłuższej sekwencji cyfr, jednym ze sposobów jest:

grep -P '(?<!\d)\d{4}(?!\d)' file

Korzysta z wyrażeń regularnych Perla , obsługiwanych przez Ubuntu grep( GNU grep ) -P. Nie będzie pasować do tekstu podobnego 12345ani nie będzie pasować do 1234ani tych, 2345które są jego częścią. Ale będzie to dopasować 1234in 1234a56789.

W wyrażeniach regularnych Perla:

  • \doznacza dowolną cyfrę (to krótki sposób powiedzieć [0-9]lub [[:digit:]]).
  • x{4}dopasowuje x4 razy. ( { }składnia nie jest specyficzna dla wyrażeń regularnych Perla; jest również w rozszerzonych wyrażeniach regularnych poprzez grep -E.) Tak \d{4}samo jest z \d\d\d\d.
  • (?<!\d)jest twierdzeniem negatywnym o zerowej szerokości. Oznacza to „chyba że poprzedza je \d”.
  • (?!\d)jest twierdzeniem o negatywnej perspektywie o zerowej szerokości. Oznacza „chyba, że ​​następuje \d”.

(?<!\d)i (?!\d)nie dopasowuj tekstu poza ciągiem czterech cyfr; zamiast tego zapobiegną (gdy zostaną użyte razem), aby nie dopasować do siebie ciągu czterech cyfr, jeśli jest on częścią dłuższej sekwencji cyfr.

Samo spojrzenie wstecz lub po prostu nie jest wystarczające, ponieważ czterocyfrowa podsekwencja znajdująca się najbardziej na prawo lub na lewo byłaby nadal dopasowywana.

Jedną z korzyści korzystania z asercji z wyprzedzeniem i z wyprzedzeniem jest to, że wzorzec pasuje tylko do samych czterocyfrowych sekwencji, a nie do otaczającego tekstu. Jest to przydatne podczas korzystania z wyróżniania kolorów (z --coloropcją).

ek@Io:~$ grep -P '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
12345abc789d0123e4

Domyślnie w Ubuntu każdy użytkownik ma alias grep='grep --color=auto'w swoim ~.bashrcpliku . Tak więc podświetlanie kolorów jest automatycznie wykonywane po uruchomieniu prostej komendy rozpoczynającej się od grep(to jest wtedy, gdy aliasy są rozwinięte), a standardowym wyjściem jest terminal (to właśnie sprawdza). Mecze są zazwyczaj podświetlane w odcieniu czerwieni (zbliżonym do cynobru ), ale pokazałem to pogrubioną kursywą. Oto zrzut ekranu:--color=auto
Zrzut ekranu przedstawiający to polecenie grep z wyjściem 12345abc789d0123e4, z 0123 podświetlonym na czerwono.

Możesz nawet grepdrukować tylko pasujący tekst, a nie całą linię, dzięki -o:

ek@Io:~$ grep -oP '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
0123

Alternatywny sposób, bez asertywności i asercji

Jeśli jednak:

  1. potrzebujesz polecenia, które będzie działać również w systemach, w których grepnie obsługuje -Plub w inny sposób nie chce używać wyrażenia regularnego Perl, i
  2. nie musisz specjalnie dopasowywać czterech cyfr - co zwykle ma miejsce, jeśli Twoim celem jest po prostu wyświetlanie wierszy zawierających dopasowania, i
  3. są w porządku z rozwiązaniem, które jest nieco mniej eleganckie

... możesz to osiągnąć za pomocą rozszerzonego wyrażenia regularnego :

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file

Dopasowuje cztery cyfry i otaczający je znak inny niż cyfra - lub początek lub koniec linii. Konkretnie:

  • [0-9]dopasowuje dowolną cyfrę (jak [[:digit:]]lub \dw wyrażeniach regularnych Perla) i {4}oznacza „cztery razy”. Tak [0-9]{4}dopasowuje sekwencję czterocyfrowy.
  • [^0-9]znaków nie pasuje w zakresie 0through 9. Jest to równoważne [^[:digit:]](lub \Dw wyrażeniach regularnych Perla).
  • ^, gdy nie pojawia się w [ ]nawiasach, dopasowuje początek linii. Podobnie, $dopasowuje koniec linii.
  • |oznacza lub nawiasy są do grupowania (jak w algebrze). Tak więc (^|[^0-9])dopasowuje początek linii lub znak niecyfrowy, a ($|[^0-9])dopasowuje koniec linii lub znak niecyfrowy.

Tak więc dopasowania występują tylko w wierszach zawierających czterocyfrową sekwencję ( [0-9]{4}), która jest jednocześnie:

  • na początku wiersza lub poprzedzony cyfrą ( (^|[^0-9])) i
  • na końcu linii lub po niej następuje cyfra ( ($|[^0-9])).

Jeśli z drugiej strony chcesz wyświetlić wszystkie wiersze zawierające czterocyfrową sekwencję, ale nie zawierają one żadnej sekwencji większej niż cztery cyfry (nawet jednej oddzielnej od innej sekwencji tylko czterech cyfr), to koncepcyjnie twoja celem jest znalezienie linii, które pasują do jednego wzoru, ale nie do drugiego.

Dlatego, nawet jeśli wiesz, jak to zrobić za pomocą jednego wzoru, sugeruję coś takiego za pomocą Matta drugiego sugestię, greping dla dwóch wzorów oddzielnie.

Robiąc to, nie korzystasz z żadnej z zaawansowanych funkcji wyrażeń regularnych Perla, więc możesz nie chcieć ich używać. Ale zgodnie z powyższym stylem, oto skrócenie rozwiązania matowego przy użyciu \d(i nawiasów klamrowych) zamiast [0-9]:

grep -P '\d{4}' file | grep -Pv '\d{5}'

Ponieważ używa [0-9], sposób Matta jest bardziej przenośny - będzie działał na systemach, w których grepnie obsługuje wyrażeń regularnych Perla. Jeśli użyjesz [0-9](lub [[:digit:]]) zamiast \d, ale nadal { }będziesz używać , uzyskasz przenośność Matta nieco bardziej zwięźle:

grep -E '[0-9]{4}' file | grep -Ev '[0-9]{5}'

Alternatywny sposób, z jednym wzorem

Jeśli naprawdę wolisz takie greppolecenie

  1. używa pojedynczego wyrażenia regularnego (nie dwóch grepoddzielonych potokiem , jak wyżej)
  2. aby wyświetlić wiersze zawierające co najmniej jedną sekwencję czterech cyfr,
  3. ale bez sekwencji pięciu (lub więcej) cyfr,
  4. i nie masz nic przeciwko dopasowaniu całej linii, nie tylko cyfr (prawdopodobnie nie masz nic przeciwko temu)

... możesz użyć:

grep -Px '(\d{0,4}\D)*\d{4}(\D\d{0,4})*' file

Te -xmarki flag grepwyświetlać tylko linie gdzie cały dopasowania linii (raczej niż jakikolwiek wiersz zawierający mecz).

Użyłem wyrażenia regularnego Perla, ponieważ uważam, że w tym przypadku zwięzłość \di \Dznacznie zwiększam jasność. Ale jeśli potrzebujesz czegoś przenośnego dla systemów, w których grepnie obsługuje -P, możesz je zastąpić za pomocą [0-9]i [^0-9](lub za pomocą [[:digit:]]i [^[:digit]]):

grep -Ex '([0-9]{0,4}[^0-9])*[0-9]{4}([^0-9][0-9]{0,4})*' file

Sposób działania tych wyrażeń regularnych jest następujący:

  • W środku \d{4}lub [0-9]{4}odpowiada jednej sekwencji czterech cyfr. Możemy mieć więcej niż jeden z nich, ale musimy mieć co najmniej jeden.

  • Po lewej stronie, (\d{0,4}\D)*lub ([0-9]{0,4}[^0-9])*dopasowuje zero lub więcej ( *) przypadki nie więcej niż czterech cyfr, a następnie non-cyfry. Zero cyfr (tj. Nic) jest jedną z możliwości dla „nie więcej niż czterech cyfr”. Odpowiada to (a) pustemu ciągowi lub (b) dowolnemu ciągowi, który kończy się cyfrą i nie zawiera żadnych sekwencji dłuższych niż cztery cyfry.

    Ponieważ tekst znajdujący się bezpośrednio po lewej stronie centralnej \d{4}(lub [0-9]{4}) musi być pusty lub kończyć się cyfrą, zapobiega to \d{4}dopasowaniu czterech cyfr, które mają inną (piątą) cyfrę po lewej stronie.

  • Po prawej stronie (\D\d{0,4})*lub ([^0-9][0-9]{0,4})*dopasowuje zero lub więcej ( *) wystąpień niecyfrowych, po których następują nie więcej niż cztery cyfry (które, podobnie jak poprzednio, mogą mieć cztery, trzy, dwie, jedną lub nawet żadną). Odpowiada to (a) pustemu ciągowi lub (b) dowolnemu ciągowi rozpoczynającemu się od cyfr i niezawierającym żadnych sekwencji dłuższych niż cztery cyfry.

    Ponieważ tekst znajdujący się bezpośrednio po prawej stronie centralnej \d{4}(lub [0-9]{4}) musi być pusty lub zaczynać się cyfrą, zapobiega to \d{4}dopasowaniu czterech cyfr, które mają inną (piątą) cyfrę tuż po prawej stronie.

Zapewnia to, że gdzieś występuje czterocyfrowa sekwencja i że nigdzie nie występuje sekwencja pięciu lub więcej cyfr.

Nie jest źle ani źle to robić w ten sposób. Ale być może najważniejszym powodem do rozważenia tej alternatywy jest wyjaśnienie korzyści z używania (lub podobnego) zamiast, jak sugerowano powyżej i w odpowiedzi Matta .grep -P '\d{4}' file | grep -Pv '\d{5}'

W ten sposób staje się jasne, że Twoim celem jest wybranie wierszy zawierających jedną rzecz, ale nie inną. Ponadto składnia jest prostsza (dlatego może być szybciej zrozumiana przez wielu czytelników / opiekunów).

Eliah Kagan
źródło
9

Spowoduje to wyświetlenie 4 liczb z rzędu, ale nie więcej

grep '[0-9][0-9][0-9][0-9][^0-9]' file

Uwaga ^ oznacza nie

Jest z tym problem, ale nie jestem pewien, jak to naprawić ... jeśli liczba jest na końcu linii, to nie pojawi się.

Ta brzydsza wersja działałaby jednak w tym przypadku

grep '[0-9][0-9][0-9][0-9]' file | grep -v [0-9][0-9][0-9][0-9][0-9]
matowy
źródło
Ups, nie musiałem być egrep - edytowałem to
mat
2
Pierwszy jest zły - znajduje a12345b, ponieważ pasuje 2345b.
Volker Siegel,
0

Jeśli grepnie obsługuje wyrażeń regularnych perla ( -P), użyj następującego polecenia powłoki:

grep -w "$(printf '[0-9]%.0s' {1..4})" file

gdzie printf '[0-9]%.0s' {1..4}wyprodukuje 4 razy [0-9]. Ta metoda jest przydatna, gdy masz długie cyfry i nie chcesz powtarzać wzoru (po prostu zamień na 4swój numer, aby wyszukać).

Używanie -wspowoduje wyszukanie całych słów. Jeśli jednak interesują Cię ciągi alfanumeryczne, takie jak 1234a, dodaj [^0-9]na końcu wzorca, np

grep "$(printf '[0-9]%.0s' {1..4})[^0-9]" file

Używanie $()jest w zasadzie zastępstwem poleceń . Sprawdź ten post, aby zobaczyć, jak printfpowtarza wzór.

kenorb
źródło
0

Możesz wypróbować poniższe polecenie, zastępując filerzeczywistą nazwą pliku w systemie:

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file

Możesz także sprawdzić ten samouczek pod kątem innych zastosowań polecenia grep.

Mike Tyson
źródło