come usare sed, awk o gawk per stampare solo ciò che è abbinato?


100

Vedo molti esempi e pagine di manuale su come eseguire operazioni come la ricerca e la sostituzione utilizzando sed, awk o gawk.

Ma nel mio caso, ho un'espressione regolare che voglio eseguire su un file di testo per estrarre un valore specifico. Non voglio fare ricerca e sostituzione. Questo viene chiamato da bash. Facciamo un esempio:

Esempio di espressione regolare:

.*abc([0-9]+)xyz.*

File di input di esempio:

a
b
c
abc12345xyz
a
b
c

Per quanto semplice possa sembrare, non riesco a capire come chiamare correttamente sed / awk / gawk. Quello che speravo di fare, è dal mio script bash avere:

myvalue=$( sed <...something...> input.txt )

Le cose che ho provato includono:

sed -e 's/.*([0-9]).*/\\1/g' example.txt # extracts the entire input file
sed -n 's/.*([0-9]).*/\\1/g' example.txt # extracts nothing

10
Wow ... la gente ha votato questa domanda verso il basso -1? È davvero così inappropriato una domanda?
Stéphane

Sembra perfettamente appropriato, usare Regex e potenti utilità da riga di comando come sed / awk o qualsiasi editor come vi, emacs o teco può essere più simile alla programmazione che al semplice utilizzo di qualche vecchia applicazione. IMO questo appartiene a SO più che a SU.
Dereleased il

Forse è stato bocciato perché nella sua forma iniziale non definiva chiaramente alcuni dei suoi requisiti. Ancora non lo fa, a meno che tu non legga i commenti dell'OP alle risposte (incluso quello che ho cancellato quando le cose sono andate a forma di pera).
padiglione

Risposte:


42

Il mio sed(Mac OS X) non funzionava con +. Ho provato *invece e ho aggiunto il ptag per la corrispondenza di stampa:

sed -n 's/^.*abc\([0-9]*\)xyz.*$/\1/p' example.txt

Per trovare almeno un carattere numerico senza + , userei:

sed -n 's/^.*abc\([0-9][0-9]*\)xyz.*$/\1/p' example.txt

Grazie, questo ha funzionato anche per me una volta che ho usato * invece di +.
Stéphane

2
... e l'opzione "p" per stampare la corrispondenza, di cui neanche io sapevo. Grazie ancora.
Stéphane

2
Ho dovuto scappare +e poi ha funzionato per me:sed -n 's/^.*abc\([0-9]\+\)xyz.*$/\1/p'
In pausa fino a nuovo avviso.

3
Questo perché non stai usando il moderno formato RE, quindi + è un carattere standard e dovresti esprimerlo con la sintassi {,}. Puoi aggiungere l'opzione use -E sed per attivare il moderno formato RE. Controlla re_format (7), in particolare l'ultimo paragrafo di DESCRIPTION developer.apple.com/library/mac/#documentation/Darwin/Reference/…
anddam

33

Puoi usare sed per farlo

 sed -rn 's/.*abc([0-9]+)xyz.*/\1/gp'
  • -n non stampare la riga risultante
  • -rquesto fa in modo che tu non abbia la possibilità di sfuggire ai genitori del gruppo di cattura ().
  • \1 la partita del gruppo di cattura
  • /g partita globale
  • /p stampare il risultato

Ho scritto uno strumento per me stesso che lo rende più facile

rip 'abc(\d+)xyz' '$1'

3
Questa è di gran lunga la risposta migliore e più ben spiegata finora!
Nik Reiman

Con qualche spiegazione, è molto meglio capire cosa c'è di sbagliato nel nostro problema. Grazie !
r4phG

17

Uso perlper rendere tutto più facile per me stesso. per esempio

perl -ne 'print $1 if /.*abc([0-9]+)xyz.*/'

Questo esegue Perl, l' -nopzione ordina a Perl di leggere una riga alla volta da STDIN ed eseguire il codice. L' -eopzione specifica l'istruzione da eseguire.

L'istruzione esegue un'espressione regolare sulla riga letta e, se corrisponde, stampa il contenuto della prima serie di parentesi graffe ($1 ).

Puoi farlo anche con più nomi di file alla fine. per esempio

perl -ne 'print $1 if /.*abc([0-9]+)xyz.*/' example1.txt example2.txt


Grazie, ma non abbiamo accesso a perl, motivo per cui ho chiesto informazioni su sed / awk / gawk.
Stéphane

5

Se la tua versione di lo grepsupporta puoi usare l' -oopzione per stampare solo la parte di qualsiasi riga che corrisponde alla tua espressione regolare.

In caso contrario, ecco il meglio che sedsono riuscito a trovare:

sed -e '/[0-9]/!d' -e 's/^[^0-9]*//' -e 's/[^0-9]*$//'

... che elimina / salta senza cifre e, per le righe rimanenti, rimuove tutti i caratteri non numerici iniziali e finali. (Immagino solo che la tua intenzione sia estrarre il numero da ogni riga che ne contiene uno).

Il problema con qualcosa come:

sed -e 's/.*\([0-9]*\).*/&/' 

.... o

sed -e 's/.*\([0-9]*\).*/\1/'

... è che sedsupporta solo la corrispondenza "avida" ... quindi il primo. * corrisponderà al resto della riga. A meno che non possiamo usare una classe di caratteri negata per ottenere una corrispondenza non avida ... o una versione di sedcompatibile con Perl o altre estensioni alle sue espressioni regolari, non possiamo estrarre una corrispondenza di pattern precisa dallo spazio del pattern (una linea ).


Puoi semplicemente combinare due dei tuoi sedcomandi in questo modo:sed -n 's/[^0-9]*\([0-9]\+\).*/\1/p'
In pausa fino a nuovo avviso.

In precedenza non conoscevo l'opzione -o su grep. Bello sapere. Ma stampa l'intera corrispondenza, non il "(...)". Quindi, se stai confrontando "abc ([[: digit:]] +) xyz", otterrai "abc" e "xyz" così come le cifre.
Stéphane

Grazie per avermelo ricordato grep -o! Stavo cercando di farlo sede ho lottato con la mia necessità di trovare più corrispondenze su alcune linee. La mia soluzione è stackoverflow.com/a/58308239/117471
Bruno Bronosky

3

È possibile utilizzare awkcon match()per accedere al gruppo acquisito:

$ awk 'match($0, /abc([0-9]+)xyz/, matches) {print matches[1]}' file
12345

Questo cerca di abbinare il modello abc[0-9]+xyz. Se lo fa, memorizza le sue sezioni nell'array matches, il cui primo elemento è il blocco [0-9]+. Poiché match() restituisce la posizione del carattere, o indice, di dove inizia quella sottostringa (1, se inizia all'inizio della stringa) , attiva l' printazione.


Con greppuoi usare uno sguardo dietro e uno sguardo avanti:

$ grep -oP '(?<=abc)[0-9]+(?=xyz)' file
12345

$ grep -oP 'abc\K[0-9]+(?=xyz)' file
12345

Questo controlla il modello [0-9]+quando si verifica all'interno abce xyzstampa solo le cifre.


2

perl è la sintassi più pulita, ma se non hai perl (non sempre presente, capisco), l'unico modo per usare gawk e componenti di un'espressione regolare è usare la funzione gensub.

gawk '/abc[0-9]+xyz/ { print gensub(/.*([0-9]+).*/,"\\1","g"); }' < file

l'output del file di input di esempio sarà

12345

Nota: gensub sostituisce l'intera regex (tra //), quindi è necessario inserire. * Prima e dopo ([0-9] +) per eliminare il testo prima e dopo il numero nella sostituzione.


2
Una soluzione intelligente e praticabile se devi (o vuoi) usare gawk. Hai notato questo, ma per essere chiari: awk non GNU non ha gensub (), e quindi non lo supporta.
cincodenada

Bello! Tuttavia, potrebbe essere meglio utilizzare match()per accedere ai gruppi acquisiti. Vedi la mia risposta per questo.
fedorqui 'SO smettila di nuocere'

1

Se vuoi selezionare le linee, elimina i bit che non vuoi:

egrep 'abc[0-9]+xyz' inputFile | sed -e 's/^.*abc//' -e 's/xyz.*$//'

Fondamentalmente seleziona le linee che desideri egrepe quindi utilizza sedper rimuovere i bit prima e dopo il numero.

Puoi vederlo in azione qui:

pax> echo 'a
b
c
abc12345xyz
a
b
c' | egrep 'abc[0-9]+xyz' | sed -e 's/^.*abc//' -e 's/xyz.*$//'
12345
pax> 

Aggiornamento: ovviamente se la tua situazione attuale è più complessa, le RE dovranno essere modificate. Ad esempio, se hai sempre un singolo numero sepolto entro zero o più non numerici all'inizio e alla fine:

egrep '[^0-9]*[0-9]+[^0-9]*$' inputFile | sed -e 's/^[^0-9]*//' -e 's/[^0-9]*$//'

Interessante ... Quindi non esiste un modo semplice per applicare un'espressione regolare complessa e recuperare solo ciò che è nella sezione (...)? Perché mentre vedo cosa hai fatto qui prima con grep e poi con sed, la nostra situazione reale è molto più complessa che eliminare "abc" e "xyz". L'espressione regolare viene utilizzata perché molti testi diversi possono apparire su entrambi i lati del testo che desidero estrarre.
Stéphane

Sono sicuro che ci sia un modo migliore se le RE sono davvero complesse. Forse se fornissi qualche esempio in più o una descrizione più dettagliata, potremmo adattare le nostre risposte.
paxdiablo

0

Il caso dell'OP non specifica che possono esserci più corrispondenze su una singola riga, ma per il traffico di Google, aggiungerò un esempio anche per questo.

Poiché la necessità dell'OP è estrarre un gruppo da un pattern, l'utilizzo grep -orichiederà 2 passaggi. Ma trovo ancora questo il modo più intuitivo per portare a termine il lavoro.

$ cat > example.txt <<TXT
a
b
c
abc12345xyz
a
abc23451xyz asdf abc34512xyz
c
TXT

$ cat example.txt | grep -oE 'abc([0-9]+)xyz'
abc12345xyz
abc23451xyz
abc34512xyz

$ cat example.txt | grep -oE 'abc([0-9]+)xyz' | grep -oE '[0-9]+'
12345
23451
34512

Poiché il tempo del processore è fondamentalmente gratuito ma la leggibilità umana non ha prezzo, tendo a rifattorizzare il mio codice in base alla domanda "tra un anno, cosa penserò che faccia?" Infatti, per il codice che intendo condividere pubblicamente o con il mio team, mi aprirò persino man grepper capire quali sono le opzioni lunghe e sostituirle. Così:grep --only-matching --extended-regexp


-1

puoi farlo con la shell

while read -r line
do
    case "$line" in
        *abc*[0-9]*xyz* ) 
            t="${line##abc}"
            echo "num is ${t%%xyz}";;
    esac
done <"file"

-3

Per awk. Userei il seguente script:

/.*abc([0-9]+)xyz.*/ {
            print $0;
            next;
            }
            {
            /* default, do nothing */
            }

Questo non restituisce il valore numerico ([0-9+]), questo restituisce l'intera riga.
Mark Lakata

-3
gawk '/.*abc([0-9]+)xyz.*/' file

2
Questo non sembra funzionare. Stampa l'intera riga invece della corrispondenza.
Stéphane

nel file di input di esempio, quel pattern è l'intera riga. giusto??? se sai che il pattern si troverà in un campo specifico: usa $ 1, $ 2 ecc. es. gawk '$ 1 ~ /.*abc([0-9”+)xyz.*/' file
ghostdog74
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.