Znajdowanie tekstu między dwoma konkretnymi znakami lub ciągami

17

Powiedz, że mam takie linie:

*[234]*
*[23]*
*[1453]*

gdzie *reprezentuje dowolny ciąg (oprócz ciągu formularza [number]). Jak parsować te linie za pomocą narzędzia wiersza poleceń i wyodrębnić liczbę między nawiasami?

Mówiąc bardziej ogólnie, które z tych narzędzi cut, sed, greplub awkbyłoby właściwe dla takiego zadania?

Amelio Vazquez-Reina
źródło

Odpowiedzi:

16

Jeśli masz GNU grep, możesz użyć jego -oopcji, aby wyszukać wyrażenie regularne i wypisać tylko pasującą część. (Inne implementacje grep mogą wyświetlać tylko całą linię.) Jeśli w jednym wierszu jest kilka dopasowań, są one drukowane w osobnych wierszach.

grep -o '\[[0-9]*\]'

Jeśli chcesz tylko cyfry, a nie nawiasy, jest to trochę trudniejsze; musisz użyć asercji o zerowej szerokości: wyrażenie regularne pasujące do pustego ciągu, ale tylko wtedy, gdy jest poprzedzone lub, w zależności od przypadku, po nawiasie. Asercje o zerowej szerokości są dostępne tylko w składni Perla.

grep -P -o '(?<=\[)[0-9]*(?=\])'

Za pomocą sed musisz wyłączyć drukowanie -ni dopasować całą linię i zachować tylko pasującą część. Jeśli w jednym wierszu jest kilka możliwych dopasowań, drukowane jest tylko ostatnie dopasowanie. Zobacz Wyodrębnianie wyrażenia regularnego dopasowanego do „sed” bez drukowania otaczających znaków, aby uzyskać więcej informacji na temat używania sed tutaj.

sed -n 's/^.*\(\[[0-9]*\]\).*/\1/p'

lub jeśli chcesz tylko cyfry, a nie nawiasy:

sed -n 's/^.*\[\([0-9]*\)\].*/\1/p'

Bez tego grep -oPerl jest tu z wyboru narzędziem, jeśli chcesz czegoś, co jest zarówno proste, jak i zrozumiałe. W każdym wierszu ( -n), jeśli wiersz zawiera dopasowanie dla \[[0-9]*\], wydrukuj to dopasowanie ( $&) i znak nowej linii ( -l).

perl -l -ne '/\[[0-9]*\]/ and print $&'

Jeśli chcesz tylko cyfr, umieść nawiasy w wyrażeniu regularnym, aby wyznaczyć grupę, i wydrukuj tylko tę grupę.

perl -l -ne '/\[([0-9]*)\]/ and print $1'

PS Jeśli chcesz wymagać tylko jednej lub więcej cyfr między nawiasami, zmień [0-9]*na [0-9][0-9]*lub na [0-9]+Perl.

Gilles „SO- przestań być zły”
źródło
Wszystko dobrze, poza tym, że chce „wyodrębnić liczbę między nawiasami”. Myślę, że „oprócz [number]” oznacza oprócz[0-9]
Peter.O,
1
@ Peter.OI rozumiałem „oprócz [liczba]”, co oznacza, że ​​nie ma innych części linii tego formularza. Ale zredagowałem swoją odpowiedź, aby pokazać, jak wydrukować tylko cyfry, na wszelki wypadek.
Gilles „SO- przestań być zły”
1
Te perlwyrażenia regularne wyglądają na naprawdę przydatne! Czytałem o nich po tym, jak zobaczyłem, że używasz zarówno twierdzeń do tyłu, jak i do przodu, nawet w grep (przełączyłem się na fakt, że możesz wybrać silnik wyrażeń regularnych). Odtąd poświęcę nieco więcej czasu na użycie wyrażenia regularnego Perla. Dzięki ... PS .. Właśnie przeczytałem man grep... "Jest to bardzo eksperymentalne i grep -P może ostrzegać przed niewdrożonymi funkcjami." ... Mam nadzieję, że to nie oznacza niestabilności (?) ...
Peter.O
5

Nie możesz tego zrobić cut.

  1. tr -c -d '0123456789\012'
  2. sed 's/[^0-9]*//g'
  3. awk -F'[^0-9]+' '{ print $1$2$3 }'
  4. grep -o -E '[0-9]+'

tr jest najbardziej naturalnym rozwiązaniem problemu i prawdopodobnie działałby najszybciej, ale myślę, że potrzebujesz gigantycznych danych wejściowych, aby oddzielić każdą z tych opcji pod względem prędkości.

Kyle Jones
źródło
Jeśli chodzi o sed, ^.*jest chciwy i zużywa wszystko oprócz ostatniej cyfry i +musi być \+albo użyć posix \([0-9][0-9]*\)... a w każdym razie 's/[^0-9]*//g'działa równie dobrze, ... Thanks for the tr -c` przykład, ale czy to nie jest \012zbyteczne?
Peter.O
@ Peter Dzięki za złapanie tego. Przysiągłbym, że przetestowałem przykład sed. :( Zmieniłem go na twoją wersję. Odnośnie \012: jest potrzebny, inaczej trzje nowe linie.
Kyle Jones
Aha ... widziałam ją jako \0, 1, 2(lub nawet \, 0, 1, 2). Wygląda na to, że nie jestem wystarczająco dostrojony do ósemki. Dzięki.
Peter.O
4

Jeśli masz na myśli wyodrębnić zestaw kolejnych cyfr między znaki nie-cyfrowych, myślę sedi awksą najlepsze (choć grepjest w stanie dać Ci dopasowane znaków):

sed: możesz oczywiście dopasować cyfry, ale być może interesujące jest zrobienie czegoś odwrotnego, usunięcie cyfr niebędących cyframi (działa, o ile jest tylko jedna liczba w linii):

$ echo nn3334nn | sed -e 's/[^[[:digit:]]]*//g'
3344

grep: możesz dopasowywać kolejne cyfry

$ echo nn3334nn | grep -o '[[:digit:]]*'
3344

Nie podam przykładu, awkponieważ nie mam z tym doświadczenia; warto zauważyć, że chociaż sedjest szwajcarskim nożem, grepdaje to prostszy, bardziej czytelny sposób, aby to zrobić, który działa również dla więcej niż jednej liczby w każdej linii wejściowej ( -otylko drukuje pasujące części wejścia, każda na własnej linii):

$ echo dna42dna54dna | grep -o '[[:digit:]]*'
42
54
njsg
źródło
Podobnie jak porównania, tutaj jest sedeqivalent z „więcej niż jeden numer na linię” przykład grep -o '[[:digit:]]*'. . . sed -nr '/[0-9]/{ s/^[^[0-9]*|[^0-9]*$//g; s/[^0-9]+/\n/g; p}'... (+1)
Peter.O
2

Ponieważ powiedziano, że nie można tego zrobić cut, pokażę, że łatwo jest stworzyć rozwiązanie, które nie jest co najmniej gorsze od niektórych innych, nawet jeśli nie popieram stosowania tego cutjako „najlepszego” (lub nawet szczególnie dobre) rozwiązanie. Należy powiedzieć, że każde rozwiązanie, które nie szuka dokładnie cyfr *[i ]*wokół cyfr, upraszcza założenia i dlatego jest podatne na błędy na przykładach bardziej złożonych niż podane przez pytającego (np. Cyfry na zewnątrz *[i ]*, których nie należy pokazywać). To rozwiązanie sprawdza przynajmniej nawiasy klamrowe i można je rozszerzyć również o gwiazdki (pozostawione jako ćwiczenie dla czytelnika):

cut -f 2 -d '[' myfile.txt | cut -f 1 -d ']'

Wykorzystuje to -dopcję określającą separator. Oczywiście możesz również cutprzesyłać do wyrażenia zamiast czytać z pliku. Chociaż cutjest prawdopodobnie dość szybki, ponieważ jest prosty (bez silnika wyrażenia regularnego), musisz wywołać go co najmniej dwa razy (lub kilka razy, aby sprawdzić *), co powoduje pewne obciążenie procesu. Jedyną prawdziwą zaletą tego rozwiązania jest to, że jest raczej czytelny, szczególnie dla zwykłych użytkowników, którzy nie są dobrze zaznajomieni z konstrukcjami regularnymi.

Tomasz
źródło