Is er een manier om sed
te 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 sed
te 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 sed
groepen 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
(-E
voor 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 grep
hebt (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 -P
schakelt Perl-compatibele reguliere expressies in. Zie man 3 pcrepattern
of 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 sed
het niet snijdt, laten we gewoon de handdoek gooien en Perl gebruiken, het is tenminste LSBterwijl grep
GNU-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-99
en 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 -o
of --only matching
en -r
of --replace
gebruikt 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.