Hoe kan ik alleen vastgelegde groepen met sed weergeven?

Is er een manier om sedte vertellen dat alleen vastgelegde groepen moeten worden uitgevoerd?

Bijvoorbeeld, gegeven de invoer:

This is a sample 123 text and some 987 numbers

En patroon:

/([\d]+)/

Kan ik alleen uitvoer 123 en 987 krijgen op de manier die is geformatteerd door back-referenties?


Antwoord 1, autoriteit 100%

De sleutel om dit te laten werken is om sedte vertellen dat je moet uitsluiten wat je niet wilt dat er wordt uitgevoerd, en ook aangeeft wat je wel wilt.

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

Dit zegt:

  • niet standaard elke regel afdrukken (-n)
  • nul of meer niet-cijfers uitsluiten
  • een of meer cijfers opnemen
  • een of meer niet-cijfers uitsluiten
  • een of meer cijfers opnemen
  • nul of meer niet-cijfers uitsluiten
  • druk de vervanging af (p)

Over het algemeen legt u in sedgroepen vast met haakjes en voert u uit wat u vastlegt met een terugverwijzing:

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

geeft “balk” weer. Als u -r(-Evoor OS X) gebruikt voor uitgebreide regex, hoeft u de haakjes niet te escapen:

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

Er kunnen maximaal 9 capture-groepen en hun back-referenties zijn. De verwijzingen naar de achterkant zijn genummerd in de volgorde waarin de groepen verschijnen, maar ze kunnen in elke volgorde worden gebruikt en kunnen worden herhaald:

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

voert “a bar a” uit.

Als je GNU grephebt (het werkt mogelijk ook in BSD, inclusief OS X):

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

of varianten zoals:

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

De optie -Pschakelt Perl-compatibele reguliere expressies in. Zie man 3 pcrepatternof man
3 pcresyntax
.


Antwoord 2, autoriteit 14%

Sed heeft maximaal negen onthouden patronen, maar je moet haakjes met escapetekens gebruiken om delen van de reguliere expressie te onthouden.

Zie hiervoor voorbeelden en meer details


Antwoord 3, autoriteit 9%

je kunt grep gebruiken

grep -Eow "[0-9]+" file

Antwoord 4, autoriteit 5%

reeks(en) van cijfers

Dit antwoord werkt met elk aantal cijfergroepen. Voorbeeld:

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

Uitgebreid antwoord.

Is er een manier om sed te vertellen om alleen vastgelegde groepen uit te voeren?

Ja. vervang alle tekst door de vastleggroep:

$ 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.

Of met uitgebreide syntaxis (minder aanhalingstekens en laat het gebruik van + toe):

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

Om te voorkomen dat de originele tekst wordt afgedrukt als er geen nummer is, gebruikt u:

$ echo 'Number xxx in text' \
   | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1/p'
  • (-n) Druk de invoer niet standaard af.
  • (/p) print alleen als er een vervanging is gedaan.

En om meerdere nummers te matchen (en ze ook af te drukken):

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

Dat werkt voor elke telling van cijferreeksen:

$ 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

Wat erg lijkt op het grep-commando:

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

Over \d

en patroon: /([\d]+)/

Sed herkent de syntaxis ‘\d’ (snelkoppeling) niet. Het hierboven gebruikte ascii-equivalent [0-9]is niet precies hetzelfde. De enige alternatieve oplossing is om een ​​tekenklasse te gebruiken: ‘[[:digit:]]`.

Het geselecteerde antwoord gebruikt dergelijke “tekenklassen” om een ​​oplossing te bouwen:

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

Die oplossing werkt alleen voor (precies) twee reeksen cijfers.

Natuurlijk, terwijl het antwoord in de shell wordt uitgevoerd, kunnen we een aantal variabelen definiëren om zo’n antwoord korter te maken:

$ 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"

Maar, zoals al is uitgelegd, is het beter om een ​​s/…/…/gp-opdracht te gebruiken:

$ 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

Dat omvat zowel herhaalde reeksen cijfers als het schrijven van een korte(re) opdracht.


Antwoord 5, autoriteit 3%

Geef het op en gebruik Perl

Aangezien sedhet niet snijdt, laten we gewoon de handdoek gooien en Perl gebruiken, het is tenminste LSBterwijl grepGNU-extensies dat niet zijn 🙂

  • Druk het volledige overeenkomende deel af, geen overeenkomende groepen of lookbehind nodig:

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

    Uitvoer:

    12
    3456
    
  • Enkele overeenkomst per regel, vaak gestructureerde gegevensvelden:

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

    Uitvoer:

    1
    34
    

    Met achterom kijken:

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

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

    Uitvoer:

    1 2
    34 56
    
  • Meerdere overeenkomsten per regel, vaak ongestructureerde gegevens:

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

    Uitvoer:

    1 
    34 78
    

    Met achterom kijken:

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

    Uitvoer:

    1
    3478
    

Antwoord 6, autoriteit 2%

Ik geloof dat het patroon in de vraag alleen als voorbeeld diende, en het doel was om elkpatroon te evenaren.

Als je een sedhebt met de GNU-extensie die het invoegen van een nieuwe regel in de patroonruimte mogelijk maakt, is een suggestie:

> 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

Deze voorbeelden zijn met tcsh (ja, ik weethet is de verkeerde shell) met CYGWIN. (Bewerken: verwijder voor bash set en de spaties rond =.)


Antwoord 7

Probeer

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"

Ik heb dit onder 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
$

Antwoord 8

Het is niet waar de OP om vroeg (groepen vastleggen), maar je kunt de cijfers extraheren met:

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

Geeft het volgende:

123
987

Antwoord 9

Ik wil een eenvoudiger voorbeeld geven van “uitvoer alleen vastgelegde groepen met sed”

Ik heb /home/me/myfile-99en wil het serienummer van het bestand uitvoeren: 99

Mijn eerste poging, die niet werkte, was:

echo "/home/me/myfile-99" | sed -r 's/myfile-(.*)$/\1/'
# output: /home/me/99

Om dit te laten werken, moeten we ook het ongewenste deel in de opnamegroep vastleggen:

echo "/home/me/myfile-99" | sed -r 's/^(.*)myfile-(.*)$/\2/'
# output: 99

*) Merk op dat sed geen \d

. heeft


Antwoord 10

Je moet een hele regel opnemen om de groep af te drukken, wat je doet bij het tweede commando, maar je hoeft het eerste jokerteken niet te groeperen. Dit zal ook werken:

echo "/home/me/myfile-99" | sed -r 's/.*myfile-(.*)$/\1/'

Antwoord 11

Je kunt ripgrepgebruiken, wat ook een sed-vervanging lijkt te zijn voor eenvoudige vervangingen, zoals deze

rg '(\d+)' -or '$1'

waarbij ripgrep -oof --only matchingen -rof --replacegebruikt om uit te voeren alleen de eerste capture-groep met $1(geciteerd om interpretatie als variabele door de shell te vermijden) twee keer vanwege twee overeenkomsten.

Other episodes