jak używać sed, awk lub gawk, aby wypisać tylko to, co jest dopasowane?


100

Widzę wiele przykładów i stron podręcznika, jak wykonywać takie czynności, jak wyszukiwanie i zamiana za pomocą sed, awk lub gawk.

Ale w moim przypadku mam wyrażenie regularne, które chcę uruchomić w pliku tekstowym, aby wyodrębnić określoną wartość. Nie chcę wyszukiwać i zamieniać. To się nazywa z bash. Posłużmy się przykładem:

Przykładowe wyrażenie regularne:

.*abc([0-9]+)xyz.*

Przykładowy plik wejściowy:

a
b
c
abc12345xyz
a
b
c

Brzmi to prosto, ale nie potrafię poprawnie wywołać sed / awk / gawk. To, co miałem nadzieję zrobić, to z poziomu mojego skryptu bash:

myvalue=$( sed <...something...> input.txt )

Rzeczy, które próbowałem, obejmują:

sed -e 's/.*([0-9]).*/\\1/g' example.txt # extracts the entire input file
sed -n 's/.*([0-9]).*/\\1/g' example.txt # extracts nothing

10
Wow ... ludzie głosowali na to pytanie w dół -1? Czy to naprawdę takie niestosowne pytanie?
— Stéphane

Wydaje się to całkowicie odpowiednie, używanie Regex i potężnych narzędzi wiersza poleceń, takich jak sed / awk lub dowolnego edytora, takiego jak vi, emacs lub teco, może być bardziej jak programowanie niż zwykłe używanie jakiejś starej aplikacji. IMO to należy do SO bardziej niż SU.
— Wydany

Być może został odrzucony, ponieważ w swojej pierwotnej formie nie określał jasno niektórych wymagań. Nadal nie działa, chyba że przeczytasz komentarze OP do odpowiedzi (w tym ten, który usunąłem, gdy sprawy przybrały kształt gruszki).
— pavium

Odpowiedzi:


42

Mój sed(Mac OS X) nie działał z +. Spróbowałem *zamiast tego i dodałem ptag do drukowania dopasowania:

sed -n 's/^.*abc\([0-9]*\)xyz.*$/\1/p' example.txt

Aby dopasować co najmniej jeden znak numeryczny bez +, użyłbym:

sed -n 's/^.*abc\([0-9][0-9]*\)xyz.*$/\1/p' example.txt

Dziękuję, to również zadziałało, gdy użyłem * zamiast +.
— Stéphane

2
... i opcja "p", aby wydrukować dopasowanie, o którym też nie wiedziałem. Dzięki jeszcze raz.
— Stéphane

2
Musiałem uciec +i wtedy to zadziałało:sed -n 's/^.*abc\([0-9]\+\)xyz.*$/\1/p'
— Wstrzymano do odwołania.

3
Dzieje się tak, ponieważ nie używasz nowoczesnego formatu RE, dlatego + jest standardowym znakiem i powinieneś to wyrazić za pomocą składni {,}. Możesz dodać opcję użyj -E sed, aby uruchomić nowoczesny format RE. Sprawdź re_format (7), a konkretnie ostatni akapit DESCRIPTION developer.apple.com/library/mac/#documentation/Darwin/Reference/…
— anddam

33

Możesz do tego użyć seda

 sed -rn 's/.*abc([0-9]+)xyz.*/\1/gp'
  • -n nie drukuj wynikowej linii
  • -rTo sprawia, że ​​nie masz ucieczki przed grupami przechwytującymi ().
  • \1 dopasowanie grupy przechwytywania
  • /g globalne dopasowanie
  • /p wydrukuj wynik

Napisałem dla siebie narzędzie, które to ułatwia

rip 'abc(\d+)xyz' '$1'

3
Jak dotąd jest to zdecydowanie najlepsza i najlepiej wyjaśniona odpowiedź!
— Nik Reiman

Po pewnym wyjaśnieniu lepiej jest zrozumieć, co jest nie tak z naszym problemem. Dziękuję Ci !
— r4phG

17

Używam, perlżeby sobie to ułatwić. na przykład

perl -ne 'print $1 if /.*abc([0-9]+)xyz.*/'

Spowoduje to uruchomienie Perla, -nopcja instruuje Perl, aby czytał po jednym wierszu na raz z STDIN i wykonywał kod. -eOpcja określa instrukcje do uruchomienia.

Instrukcja uruchamia wyrażenie regularne w przeczytanym wierszu i jeśli pasuje, wypisuje zawartość pierwszego zestawu nawiasów ( $1).

Możesz to zrobić, jeśli na końcu pojawi się wiele nazw plików. na przykład

perl -ne 'print $1 if /.*abc([0-9]+)xyz.*/' example1.txt example2.txt


Dzięki, ale nie mamy dostępu do perla, dlatego pytałem o sed / awk / gawk.
— Stéphane

5

Jeśli twoja wersja grepobsługuje to, możesz użyć -oopcji drukowania tylko części dowolnego wiersza, która pasuje do twojego wyrażenia regularnego.

Jeśli nie, oto najlepsze, sedjakie mogłem wymyślić:

sed -e '/[0-9]/!d' -e 's/^[^0-9]*//' -e 's/[^0-9]*$//'

... który usuwa / pomija bez cyfr, a dla pozostałych wierszy usuwa wszystkie początkowe i końcowe znaki niebędące cyframi. (Domyślam się tylko, że twoim zamiarem jest wyodrębnienie liczby z każdego wiersza, który zawiera jeden).

Problem z czymś takim:

sed -e 's/.*\([0-9]*\).*/&/' 

.... lub

sed -e 's/.*\([0-9]*\).*/\1/'

... jest to, że sedobsługuje tylko "zachłanne" dopasowanie ... więc pierwsza. * będzie pasować do reszty linii. O ile nie możemy użyć zanegowanej klasy znaków, aby osiągnąć niechciwe dopasowanie ... lub wersję sedz kompatybilnymi z Perl lub innymi rozszerzeniami do jej wyrażeń regularnych, nie możemy wyodrębnić dokładnego dopasowania wzorca z przestrzeni wzorców (linia ).


Możesz po prostu połączyć dwa swoje sedpolecenia w ten sposób:sed -n 's/[^0-9]*\([0-9]\+\).*/\1/p'
— Wstrzymano do odwołania.

Wcześniej nie wiedziałem o opcji -o w grep. Dobrze wiedzieć. Ale wypisuje cały mecz, a nie „(...)”. Więc jeśli dopasowujesz na „abc ([: digit:]] +) xyz”, otrzymasz „abc” i „xyz”, a także cyfry.
— Stéphane

Dzięki za przypomnienie mi grep -o! Próbowałem to zrobić sedi walczyłem z moją potrzebą znalezienia wielu dopasowań na niektórych liniach. Moje rozwiązanie to stackoverflow.com/a/58308239/117471
— Bruno Bronosky

3

Możesz użyć awkz, match()aby uzyskać dostęp do przechwyconej grupy:

$ awk 'match($0, /abc([0-9]+)xyz/, matches) {print matches[1]}' file
12345

To próbuje dopasować wzorzec abc[0-9]+xyz. Jeśli to zrobi, przechowuje swoje wycinki w tablicy matches, której pierwszym elementem jest blok [0-9]+. Ponieważ match() zwraca pozycję znaku lub indeks miejsca, w którym zaczyna się ten podciąg (1, jeśli zaczyna się na początku ciągu) , wyzwala printakcję.


Dzięki grepmożesz użyć patrzenia wstecz i przewidywania:

$ grep -oP '(?<=abc)[0-9]+(?=xyz)' file
12345

$ grep -oP 'abc\K[0-9]+(?=xyz)' file
12345

Sprawdza to wzór [0-9]+, gdy zachodzi wewnątrz abci xyzi drukuje tylko cyfry.


2

perl to najczystsza składnia, ale jeśli nie masz perla (rozumiem, że nie zawsze tam jest), jedynym sposobem użycia gawk i składników wyrażenia regularnego jest użycie funkcji gensub.

gawk '/abc[0-9]+xyz/ { print gensub(/.*([0-9]+).*/,"\\1","g"); }' < file

wyjście z przykładowego pliku wejściowego będzie

12345

Uwaga: gensub zamienia całe wyrażenie regularne (między //), więc musisz wstawić. * Przed i po ([0-9] +), aby pozbyć się tekstu przed i po liczbie w podstawieniu.


2
Sprytne, wykonalne rozwiązanie, jeśli musisz (lub chcesz) używać gawk. Zauważyłeś to, ale żeby było jasne: awk inny niż GNU nie ma funkcji gensub (), a zatem nie obsługuje tego.
— cincodenada

Miły! Najlepiej jednak użyć, match()aby uzyskać dostęp do przechwyconych grup. Zobacz moją odpowiedź na to.
— fedorqui 'SO przestać szkodzić'

1

Jeśli chcesz zaznaczyć linie, usuń niepotrzebne fragmenty:

egrep 'abc[0-9]+xyz' inputFile | sed -e 's/^.*abc//' -e 's/xyz.*$//'

Zasadniczo wybiera żądane linie, egrepa następnie używa seddo usunięcia bitów przed i po liczbie.

Możesz to zobaczyć w akcji tutaj:

pax> echo 'a
b
c
abc12345xyz
a
b
c' | egrep 'abc[0-9]+xyz' | sed -e 's/^.*abc//' -e 's/xyz.*$//'
12345
pax> 

Aktualizacja: oczywiście, jeśli twoja rzeczywista sytuacja jest bardziej złożona, RE będą musiały mnie zmodyfikować. Na przykład, jeśli zawsze miałeś jedną liczbę ukrytą w obrębie zera lub większej liczby liczb nienumerycznych na początku i na końcu:

egrep '[^0-9]*[0-9]+[^0-9]*$' inputFile | sed -e 's/^[^0-9]*//' -e 's/[^0-9]*$//'

Interesujące ... Więc nie ma prostego sposobu na zastosowanie złożonego wyrażenia regularnego i odzyskanie tego, co jest w sekcji (...)? Ponieważ widzę, co zrobiłeś najpierw z grep, a potem z sedem, nasza prawdziwa sytuacja jest znacznie bardziej złożona niż porzucenie "abc" i "xyz". Wyrażenie regularne jest używane, ponieważ po obu stronach tekstu, który chcę wyodrębnić, może pojawić się wiele różnych tekstów.
— Stéphane

Jestem pewien, że jest lepszy sposób, jeśli RE są naprawdę złożone. Być może gdybyś podał więcej przykładów lub bardziej szczegółowy opis, moglibyśmy dopasować nasze odpowiedzi.
— paxdiablo

0

Przypadek OP nie określa, że ​​może istnieć wiele dopasowań w jednym wierszu, ale dla ruchu Google dodam również przykład.

Ponieważ potrzebą OP jest wyodrębnienie grupy ze wzoru, użycie grep -obędzie wymagało 2 przejść. Ale nadal uważam, że jest to najbardziej intuicyjny sposób wykonania pracy.

$ cat > example.txt <<TXT
a
b
c
abc12345xyz
a
abc23451xyz asdf abc34512xyz
c
TXT

$ cat example.txt | grep -oE 'abc([0-9]+)xyz'
abc12345xyz
abc23451xyz
abc34512xyz

$ cat example.txt | grep -oE 'abc([0-9]+)xyz' | grep -oE '[0-9]+'
12345
23451
34512

Ponieważ czas procesora jest w zasadzie wolny, ale czytelność dla człowieka jest bezcenna, mam tendencję do refaktoryzacji kodu w oparciu o pytanie „za rok od teraz, co myślę, że to robi?” W rzeczywistości w przypadku kodu, który zamierzam udostępnić publicznie lub swojemu zespołowi, otworzę nawet, man grepaby dowiedzieć się, jakie są długie opcje i je zastąpić. Tak jak to:grep --only-matching --extended-regexp


-1

możesz to zrobić z muszlą

while read -r line
do
    case "$line" in
        *abc*[0-9]*xyz* ) 
            t="${line##abc}"
            echo "num is ${t%%xyz}";;
    esac
done <"file"

-3

Dla awk. Użyłbym następującego skryptu:

/.*abc([0-9]+)xyz.*/ {
            print $0;
            next;
            }
            {
            /* default, do nothing */
            }

To nie wyprowadza wartości liczbowej ([0-9+]), to wyświetla całą linię.
— Mark Lakata

-3
gawk '/.*abc([0-9]+)xyz.*/' file

2
To nie działa. Drukuje całą linię zamiast dopasowania.
— Stéphane

w przykładowym pliku wejściowym tym wzorcem jest cała linia. dobrze??? jeśli wiesz, że wzorzec będzie w określonym polu: użyj 1 $, 2 $ itd .. np. gawk '$ 1 ~ /.*abc([0-9]+)xyz.*/' file
— ghostdog74
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.