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.