Come produrre solo gruppi acquisiti con sed?


278

C'è un modo per dire seddi produrre solo gruppi acquisiti? Ad esempio, dato l'input:

This is a sample 123 text and some 987 numbers

e modello:

/([\d]+)/

Potrei ottenere solo 123 e 987 output nel modo formattato da riferimenti posteriori?


Nota, l'acquisizione di gruppo richiede sedl'attivazione di espressioni regolari estese con il -Eflag.
Peter - Ripristina Monica il

Risposte:


333

La chiave per far funzionare tutto ciò è dire seddi escludere ciò che non si desidera produrre e specificare ciò che si desidera.

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

Questo dice:

  • non stampare per default ogni riga ( -n)
  • escludere zero o più non cifre
  • includere una o più cifre
  • escludere una o più non cifre
  • includere una o più cifre
  • escludere zero o più non cifre
  • stampa la sostituzione ( p)

In generale, in sedte acquisisci gruppi usando le parentesi e produci ciò che catturi usando un riferimento indietro:

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

produrrà "bar". Se si utilizza -r( -Eper OS X) per regex esteso, non è necessario sfuggire alle parentesi:

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

Possono esserci fino a 9 gruppi di acquisizione e i loro riferimenti posteriori. I riferimenti posteriori sono numerati nell'ordine in cui appaiono i gruppi, ma possono essere utilizzati in qualsiasi ordine e possono essere ripetuti:

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

genera "a bar a".

Se hai GNU grep(potrebbe funzionare anche in BSD, incluso OS X):

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

o variazioni come:

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

L' -Popzione abilita le espressioni regolari compatibili Perl. Vedi man 3 pcrepatterno man 3 pcresyntax.


24
Come nota, OSX Mountain Lion non supporta più PCRE in grep.
Yincrash,

1
Come nota a margine, l'opzione grep -o non è supportata su Solaris 9. Inoltre, Solaris 9 non supporta l'opzione sed -r. :(
Daniel Kats,

7
Chiedi al tuo amministratore di sistema di installare gsed.
Sareste

3
Nota che potresti dover aggiungere il prefisso "(" e ")" con "\", non so perché.
lumbric

7
@lumbric: se ti riferisci sedall'esempio, se usi l' -ropzione (o -Eper OS X, IIRC) non devi sfuggire alle parentesi. La differenza è tra espressioni regolari di base ed espressioni regolari estese ( -r).
In pausa fino a nuovo avviso.

55

Sed ha fino a nove schemi ricordati, ma è necessario usare parentesi sfuggite per ricordare parti dell'espressione regolare.

Vedi qui per esempi e maggiori dettagli


58
sed -e 's/version=\(.+\)/\1/' input.txtquesto genererà comunque l'intero input.txt
Pablo

@Pablo, Nel tuo schema devi scrivere \+invece di +. E non capisco perché le persone usano -esolo un comando sed.
Fredrick Gauss,

1
uso sed -e -n 's/version=\(.+\)/\1/p' input.txtvedi: mikeplate.com/2012/05/09/…
awattar

1
Suggerirei di usare sed -Eper usare le espressioni cosiddette "moderne" o "estese" che sembrano molto più vicine a Perl / Java / JavaScript / Go / qualunque sia il sapore. (Confronta con grep -Eo egrep.) La sintassi predefinita ha quelle strane regole di escape ed è considerata "obsoleta". Per ulteriori informazioni sulle differenze tra i due, eseguire man 7 re_format.
AndrewF,

31

puoi usare grep

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

4
@ ghostdog74: assolutamente d'accordo con te. Come posso ottenere greo per produrre solo gruppi acquisiti?
Pablo,

1
@Michael - ecco perché l' oopzione è lì - unixhelp.ed.ac.uk/CGI/man-cgi?grep : -o, --only matching Mostra solo la parte di una riga corrispondente che corrisponde a PATTERN
Bert F

14
@ Bert F: capisco la parte corrispondente, ma non è il gruppo di acquisizione. Quello che voglio è avere questo ([0-9] +). + ([Abc] {2,3}), quindi ci sono 2 gruppi di acquisizione. Voglio produrre SOLO gruppi di acquisizione tramite riferimenti indietro o in qualche altro modo.
Pablo,

Ciao Michael. Sei riuscito a estrarre l'ennesimo gruppo catturato da grep?
doc_id

1
@Pablo: grep sta solo producendo ciò che corrisponde. Per assegnarlo a più gruppi, usa più espressioni: grep -Eow -e "[0-9]+" -e "[abc]{2,3}"non so come potresti richiedere che queste due espressioni siano su una riga a parte il piping di un grep precedente (che potrebbe comunque non funzionare se uno dei pattern corrisponde più di una volta su una riga ).
idbrii,

13

corsa (e) di cifre

Questa risposta funziona con qualsiasi conteggio dei gruppi di cifre. Esempio:

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

Risposta estesa.

C'è un modo per dire a sed di produrre solo gruppi acquisiti?

Sì. sostituisci tutto il testo con il gruppo di acquisizione:

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

O con sintassi estesa (meno backquotes e consenti l'uso di +):

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

Per evitare di stampare il testo originale in assenza di numeri, utilizzare:

$ echo 'Number xxx in text' | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1/p'
  • (-n) Non stampa l'input per impostazione predefinita.
  • (/ p) stampa solo se è stata effettuata una sostituzione.

E per abbinare più numeri (e anche stamparli):

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

Funziona con qualsiasi conteggio delle esecuzioni delle cifre:

$ 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

Che è molto simile al comando grep:

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

Circa \ d

e modello: /([\d]+)/

Sed non riconosce la sintassi '\ d' (scorciatoia). L'equivalente ascii usato sopra [0-9]non è esattamente equivalente. L'unica soluzione alternativa è usare una classe di caratteri: '[[: digit:]] `.

La risposta selezionata utilizza tali "classi di caratteri" per creare una soluzione:

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

Questa soluzione funziona solo per (esattamente) due serie di cifre.

Naturalmente, poiché la risposta viene eseguita all'interno della shell, possiamo definire un paio di variabili per ridurre tale risposta:

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

Ma, come è già stato spiegato, s/…/…/gpè meglio usare un comando:

$ 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

Ciò riguarderà sia le ripetute serie di cifre sia la scrittura di un breve comando (er).


Sorpreso dopo aver letto la risposta accettata e votata per la massima votazione, ho scorrere verso il basso per scrivere sul suo ambito ristretto e per affrontare effettivamente lo spirito della domanda. Avrei dovuto immaginare che qualcuno l'avrebbe già fatto anni fa. Questo è molto ben spiegato ed è la vera risposta corretta.
Amit Naidu,

9

Credo che lo schema indicato nella domanda fosse solo a scopo esemplificativo e l'obiettivo era quello di abbinare qualsiasi schema.

Se hai un sed con l'estensione GNU che consente l'inserimento di una nuova riga nello spazio del pattern, un suggerimento è:

> 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

Questi esempi sono con tcsh (sì, lo so è la shell sbagliata) con CYGWIN. (Modifica: per bash, rimuovi set e gli spazi intorno a =.)


@Joseph: grazie, comunque, in base al mio compito mi sembra che grep sia più naturale, come suggerito ghostdog74. Devo solo capire come rendere grep in output solo i gruppi di acquisizione, non l'intera corrispondenza.
Pablo,

2
Solo una nota, ma il segno più '+' significa 'uno o più' che eliminerebbe la necessità di ripetersi negli schemi. Quindi, "[0-9] [0-9] *" diventerebbe "[0-9] +"
RandomInsano,

4
@RandomInsano: per usare il +, dovresti fuggire o usare l' -ropzione ( -Eper OS X). Puoi anche usare \{1,\}(o -ro -Esenza la fuga).
In pausa fino a nuovo avviso.

9

Rinuncia e usa Perl

Dal momento che sednon lo taglia, buttiamo semplicemente l'asciugamano e usiamo Perl, almeno è LSB mentre le grepestensioni GNU non sono :-)

  • Stampa l'intera parte corrispondente, nessun gruppo corrispondente o lookbehind necessario:

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

    Produzione:

    12
    3456
  • Corrispondenza singola per riga, campi dati spesso strutturati:

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

    Produzione:

    1
    34

    Con lookbehind:

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

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

    Produzione:

    1 2
    34 56
  • Partite multiple per riga, dati spesso non strutturati:

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

    Produzione:

    1 
    34 78

    Con lookbehind:

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

    Produzione:

    1
    3478

1
Cosa non hai avuto con la fine della domanda: "con sed"?
Moonchild,

Ai googler di @Moonchild non importa.
Ciro Santilli 5 冠状 病 六四 事件 法轮功

1
l'ho trovato utile. non tutti i problemi di regex da riga di comando devono essere risolti con sed.
PPPaul

5

Provare

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"

Ho ottenuto questo sotto 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
$

2

Non è ciò che l'OP ha richiesto (gruppi di acquisizione) ma è possibile estrarre i numeri utilizzando:

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

Dà quanto segue:

123
987
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.