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
Stéphane
źródło
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
mouviciel
źródło
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'
Ilia Choly
źródło
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

PP.
źródło
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 ).

Jim Dennis
źródło
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.

fedorqui 'SO przestań szkodzić'
źródło
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.

Mark Lakata
źródło
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]*$//'
paxdiablo
źródło
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

Bruno Bronosky
źródło
-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"
ghostdog74
źródło
-3

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

/.*abc([0-9]+)xyz.*/ {
            print $0;
            next;
            }
            {
            /* default, do nothing */
            }
Pierre
źródło
To nie wyprowadza wartości liczbowej ([0-9+]), to wyświetla całą linię.
Mark Lakata
-3
gawk '/.*abc([0-9]+)xyz.*/' file
ghostdog74
źródło
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