Jak generować tylko przechwycone grupy za pomocą sed?

277

Czy jest jakiś sposób na przekazanie informacji sedtylko wychwyconym grupom? Na przykład biorąc pod uwagę dane wejściowe:

This is a sample 123 text and some 987 numbers

i wzór:

/([\d]+)/

Czy mogę uzyskać tylko dane wyjściowe 123 i 987 w sposób sformatowany przez odwołania zwrotne?

Pablo
źródło
Uwaga: przechwytywanie grupowe wymaga sedwłączenia rozszerzonych wyrażeń regularnych z -Eflagą.
peterh - Przywróć Monikę

Odpowiedzi:

333

Kluczem do tego, aby zadziałało, jest sedwykluczenie tego, czego nie chcesz, aby dane wyjściowe, a także określenie tego, czego chcesz.

string='This is a sample 123 text and some 987 numbers'
echo "$string" | sed -rn 's/[^[:digit:]]*([[:digit:]]+)[^[:digit:]]+([[:digit:]]+)[^[:digit:]]*/\1 \2/p'

To mówi:

  • nie domyślnie drukuje każdej linii ( -n)
  • wyklucz zero lub więcej cyfr
  • zawierać jedną lub więcej cyfr
  • wyklucz jedną lub więcej cyfr
  • zawierać jedną lub więcej cyfr
  • wyklucz zero lub więcej cyfr
  • wydrukuj podstawienie ( p)

Ogólnie rzecz biorąc, sedprzechwytujesz grupy za pomocą nawiasów i wyświetlasz to, co przechwytujesz, korzystając z referencji wstecz:

echo "foobarbaz" | sed 's/^foo\(.*\)baz$/\1/'

wyświetli „pasek”. Jeśli używasz -r( -Edla OS X) rozszerzonego wyrażenia regularnego, nie musisz uciekać z nawiasów:

echo "foobarbaz" | sed -r 's/^foo(.*)baz$/\1/'

Może istnieć do 9 grup przechwytywania i ich referencje. Odwołania wsteczne są ponumerowane w kolejności, w jakiej pojawiają się grupy, ale można ich używać w dowolnej kolejności i można je powtarzać:

echo "foobarbaz" | sed -r 's/^foo(.*)b(.)z$/\2 \1 \2/'

wyprowadza „słupek a”.

Jeśli masz GNU grep(może również działać w BSD, w tym OS X):

echo "$string" | grep -Po '\d+'

lub odmiany, takie jak:

echo "$string" | grep -Po '(?<=\D )(\d+)'

Ta -Popcja włącza wyrażenia regularne zgodne z Perlem. Zobacz man 3 pcrepatternlub man 3 pcresyntax.

Wstrzymano do odwołania.
źródło
24
Uwaga: Mountain Lion OSX nie obsługuje już PCRE w grep.
yincrash,
1
Na marginesie, opcja grep -o nie jest obsługiwana w systemie Solaris 9. Ponadto Solaris 9 nie obsługuje opcji sed -r. :(
Daniel Kats
7
Poproś administratora systemu o zainstalowanie gsed. Zdziwiłbyś się, co dostaniesz za kilka pączków ...
avgvstvs
3
Zauważ, że może być konieczne poprzedzenie „(” i „)” „\”, nie wiem dlaczego.
lędźwiowy
7
@lumbric: Jeśli odwołujesz się do sedprzykładu, jeśli korzystasz z -ropcji (lub -Edla OS X, IIRC), nie musisz uciekać od nawiasów. Różnica polega na tym, że między podstawowymi wyrażeniami regularnymi a rozszerzonymi wyrażeniami regularnymi ( -r).
Wstrzymano do odwołania.
55

Sed ma do dziewięciu zapamiętanych wzorców, ale musisz użyć nawiasów ucieczkowych, aby zapamiętać fragmenty wyrażenia regularnego.

Zobacz tutaj przykłady i więcej szczegółów

Peter McG
źródło
58
sed -e 's/version=\(.+\)/\1/' input.txtto wciąż wypisze cały input.txt
Pablo
@Pablo, W swoim wzorze musisz pisać \+zamiast +. I nie rozumiem, dlaczego ludzie używają -etylko jednego polecenia sed.
Fredrick Gauss,
1
użyj sed -e -n 's/version=\(.+\)/\1/p' input.txtzobacz: mikeplate.com/2012/05/09/…
awattar
1
Sugeruję sed -Eużycie tak zwanych „nowoczesnych” lub „rozszerzonych” wyrażeń regularnych, które wyglądają znacznie bliżej Perl / Java / JavaScript / Go / cokolwiek innego. (Porównaj z grep -Elub egrep.) Domyślna składnia ma dziwne reguły ucieczki i jest uważana za „przestarzałą”. Aby uzyskać więcej informacji na temat różnic między nimi, uruchom man 7 re_format.
AndrewF,
31

możesz użyć grep

grep -Eow "[0-9]+" file
ghostdog74
źródło
4
@ ghostdog74: Absolutnie się z tobą zgadzam. Jak mogę ustawić greo, aby wyświetlał tylko przechwycone grupy?
Pablo
1
@Michael - dlatego oistnieje taka opcja - unixhelp.ed.ac.uk/CGI/man-cgi?grep : -o, - tylko dopasowanie Dopasuj tylko część pasującego wiersza pasującego WZORZEC
Bert F
14
@Bert F: Rozumiem pasującą część, ale nie jest to grupa przechwytująca. Chcę to mieć tak ([0-9] +). + ([Abc] {2,3}), aby były 2 grupy przechwytywania. Chcę wyprowadzać TYLKO grupy przechwytywania według odsyłaczy lub w jakiś inny sposób.
Pablo
Cześć Michał. Czy udało ci się wyodrębnić n-tą przechwyconą grupę przez grep?
doc_id
1
@Pablo: grep wyświetla tylko to, co pasuje. Aby nadać mu wiele grup, użyj wielu wyrażeń: grep -Eow -e "[0-9]+" -e "[abc]{2,3}"Nie wiem, jak możesz wymagać, aby te dwa wyrażenia znajdowały się w jednym wierszu oprócz pipingu z poprzedniego grep (który wciąż nie mógłby działać, jeśli którykolwiek wzorzec pasuje więcej niż jeden raz w wierszu ).
idbrii
13

liczba cyfr

Ta odpowiedź działa z dowolną liczbą grup cyfr. Przykład:

$ echo 'Num123that456are7899900contained0018166intext' |
> sed -En 's/[^0-9]*([0-9]{1,})[^0-9]*/\1 /gp'
123 456 7899900 0018166

Rozszerzona odpowiedź.

Czy jest jakiś sposób, aby powiedzieć sedowi, aby wysyłał tylko przechwycone grupy?

Tak. zastąp cały tekst grupą przechwytywania:

$ echo 'Number 123 inside text' | sed 's/[^0-9]*\([0-9]\{1,\}\)[^0-9]*/\1/'
123

s/[^0-9]*                           # several non-digits
         \([0-9]\{1,\}\)            # followed by one or more digits
                        [^0-9]*     # and followed by more non-digits.
                               /\1/ # gets replaced only by the digits.

Lub z rozszerzoną składnią (mniej cudzysłowów i zezwól na użycie +):

$ echo 'Number 123 in text' | sed -E 's/[^0-9]*([0-9]+)[^0-9]*/\1/'
123

Aby uniknąć drukowania oryginalnego tekstu, gdy nie ma numeru, użyj:

$ echo 'Number xxx in text' | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1/p'
  • (-n) Domyślnie nie drukuj danych wejściowych.
  • (/ p) drukuj tylko wtedy, gdy dokonano wymiany.

Aby dopasować kilka liczb (a także je wydrukować):

$ echo 'N 123 in 456 text' | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1 /gp'
123 456

Działa to dla dowolnej liczby przebiegów cyfrowych:

$ str='Test Num(s) 123 456 7899900 contained as0018166df in text'
$ echo "$str" | sed -En 's/[^0-9]*([0-9]{1,})[^0-9]*/\1 /gp'
123 456 7899900 0018166

Co jest bardzo podobne do polecenia grep:

$ str='Test Num(s) 123 456 7899900 contained as0018166df in text'
$ echo "$str" | grep -Po '\d+'
123
456
7899900
0018166

Informacje o \ d

i wzór: /([\d]+)/

Sed nie rozpoznaje składni „\ d” (skrót). Stosowany powyżej ekwiwalent ascii [0-9]nie jest dokładnie równoważny. Jedynym alternatywnym rozwiązaniem jest użycie klasy znaków: „[[: digit:]]„.

Wybrana odpowiedź używa takich „klas znaków” do zbudowania rozwiązania:

$ str='This is a sample 123 text and some 987 numbers'
$ echo "$str" | sed -rn 's/[^[:digit:]]*([[:digit:]]+)[^[:digit:]]+([[:digit:]]+)[^[:digit:]]*/\1 \2/p'

To rozwiązanie działa tylko dla (dokładnie) dwóch serii cyfr.

Oczywiście, ponieważ odpowiedź jest wykonywana wewnątrz powłoki, możemy zdefiniować kilka zmiennych, aby skrócić taką odpowiedź:

$ str='This is a sample 123 text and some 987 numbers'
$ d=[[:digit:]]     D=[^[:digit:]]
$ echo "$str" | sed -rn "s/$D*($d+)$D+($d+)$D*/\1 \2/p"

Ale, jak już wyjaśniono, s/…/…/gplepsze jest użycie polecenia:

$ str='This is 75577 a sam33ple 123 text and some 987 numbers'
$ d=[[:digit:]]     D=[^[:digit:]]
$ echo "$str" | sed -rn "s/$D*($d+)$D*/\1 /gp"
75577 33 123 987

Obejmuje to zarówno powtarzające się serie cyfr, jak i pisanie krótkich (er) komend.

Izaak
źródło
Zaskoczony po przeczytaniu wysoko głosowanej zaakceptowanej odpowiedzi przewinąłem w dół, aby napisać o jej wąskim zakresie i faktycznie odpowiedzieć na ducha pytania. Powinienem zgadnąć, że ktoś zrobiłby to już wiele lat temu. Jest to bardzo dobrze wyjaśnione i jest to prawdziwa poprawna odpowiedź.
Amit Naidu
9

Uważam, że wzorzec podany w pytaniu był jedynie przykładowy, a celem było dopasowanie dowolnego wzorca.

Jeśli masz sed z rozszerzeniem GNU pozwalającym na wstawienie nowego wiersza w przestrzeni wzorów, jedną z sugestii jest:

> set string = "This is a sample 123 text and some 987 numbers"
>
> set pattern = "[0-9][0-9]*"
> echo $string | sed "s/$pattern/\n&\n/g" | sed -n "/$pattern/p"
123
987
> set pattern = "[a-z][a-z]*"
> echo $string | sed "s/$pattern/\n&\n/g" | sed -n "/$pattern/p"
his
is
a
sample
text
and
some
numbers

Te przykłady są z tcsh (tak, wiem, że to zła powłoka) z CYGWIN. (Edycja: Dla bash, usuń set, a spacje wokół =.)

Joseph Quinsey
źródło
@Joseph: dzięki, jednak w oparciu o moje zadanie czuję, że grep jest bardziej naturalny, jak sugeruje ghostdog74. Wystarczy dowiedzieć się, jak sprawić, by grep wyprowadzał tylko grupy przechwytywania, a nie całe dopasowanie.
Pablo
2
Tylko uwaga, ale znak plus „+” oznacza „jeden lub więcej”, co wyeliminowałoby potrzebę powtarzania się we wzorach. Tak więc „[0–9] [0–9] *” zmieni się na „[0–9] +”
RandomInsano
4
@RandomInsano: Aby użyć +, musisz uciec z niego lub skorzystać z -ropcji ( -Edla OS X). Możesz także użyć \{1,\}( -rlub -Ebez ucieczki).
Wstrzymano do odwołania.
9

Zrezygnuj i użyj Perla

Ponieważ sedgo nie tnie, po prostu rzućmy ręcznikiem i użyjmy Perla, przynajmniej LSB, podczas gdy greprozszerzenia GNU nie są :-)

  • Wydrukuj całą pasującą część, nie trzeba dopasowywać grup ani szukać:

    cat <<EOS | perl -lane 'print m/\d+/g'
    a1 b2
    a34 b56
    EOS

    Wynik:

    12
    3456
  • Pojedyncze dopasowanie w wierszu, często uporządkowane pola danych:

    cat <<EOS | perl -lape 's/.*?a(\d+).*/$1/g'
    a1 b2
    a34 b56
    EOS

    Wynik:

    1
    34

    Z lookbehind:

    cat <<EOS | perl -lane 'print m/(?<=a)(\d+)/'
    a1 b2
    a34 b56
    EOS
  • Wiele pól:

    cat <<EOS | perl -lape 's/.*?a(\d+).*?b(\d+).*/$1 $2/g'
    a1 c0 b2 c0
    a34 c0 b56 c0
    EOS

    Wynik:

    1 2
    34 56
  • Wiele dopasowań w wierszu, często nieustrukturyzowane dane:

    cat <<EOS | perl -lape 's/.*?a(\d+)|.*/$1 /g'
    a1 b2
    a34 b56 a78 b90
    EOS

    Wynik:

    1 
    34 78

    Z lookbehind:

    cat EOS<< | perl -lane 'print m/(?<=a)(\d+)/g'
    a1 b2
    a34 b56 a78 b90
    EOS

    Wynik:

    1
    3478
Ciro Santilli
źródło
1
Czego nie dostałeś na końcu pytania: „z sed”?
Moonchild
@Moonchild Googlers nie obchodzi.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
uznałem to za przydatne. nie wszystkie problemy z wyrażeniami regularnymi w wierszu poleceń należy rozwiązać za pomocą sed.
PPPaul
5

Próbować

sed -n -e "/[0-9]/s/^[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*$/\1 \2 \3 \4 \5 \6 \7 \8 \9/p"

Mam to pod cygwin:

$ (echo "asdf"; \
   echo "1234"; \
   echo "asdf1234adsf1234asdf"; \
   echo "1m2m3m4m5m6m7m8m9m0m1m2m3m4m5m6m7m8m9") | \
  sed -n -e "/[0-9]/s/^[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*$/\1 \2 \3 \4 \5 \6 \7 \8 \9/p"

1234
1234 1234
1 2 3 4 5 6 7 8 9
$
Bert F.
źródło
2

Nie o to prosił PO (przechwytywanie grup), ale możesz wyodrębnić liczby, używając:

S='This is a sample 123 text and some 987 numbers'
echo "$S" | sed 's/ /\n/g' | sed -r '/([0-9]+)/ !d'

Daje następujące:

123
987
Thomas Bratt
źródło