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.