Come posso "grep" motivi su più linee?


24

Sembra che sto abusando grep/ egrep.

Stavo cercando di cercare stringhe su più righe e non sono riuscito a trovare una corrispondenza mentre so che ciò che sto cercando dovrebbe corrispondere. Inizialmente pensavo che i miei regex fossero sbagliati, ma alla fine ho letto che questi strumenti funzionano per riga (anche i miei regex erano così banali che non poteva essere il problema).

Quindi quale strumento si dovrebbe usare per cercare schemi su più righe?



1
@CiroSantilli - Non penso che questa Q e quella a cui ti sei collegato siano duplicati. L'altra Q sta chiedendo come si farebbe la corrispondenza del pattern multilinea (ovvero quale strumento dovrei / posso usare per fare questo) mentre questo sta chiedendo come farlo grep. Sono strettamente correlati ma non dups, IMO.
slm

@sim quei casi sono difficili da decidere: vedo il tuo punto. Penso che questo caso particolare sia migliore come duplicato perché l'utente ha detto "grep"suggerendo il verbo "grep", e le risposte migliori, incluso accettato, non usano grep.
Ciro Santilli 16 改造 中心 法轮功 六四 事件

Risposte:


24

Eccone seduno che ti darà un grepcomportamento simile a più righe:

sed -n '/foo/{:start /bar/!{N;b start};/your_regex/p}' your_file

Come funziona

  • -n sopprime il comportamento predefinito della stampa di ogni riga
  • /foo/{}gli ordina di abbinare fooe fare ciò che viene dentro gli squiggli alle linee corrispondenti. Sostituire foocon la parte iniziale del modello.
  • :start è un'etichetta di ramificazione che ci aiuta a mantenere il loop fino a quando non troviamo la fine della nostra regex.
  • /bar/!{}eseguirà ciò che è negli squiggli alle linee che non corrispondono bar. Sostituisci barcon la parte finale del modello.
  • Naggiunge la riga successiva al buffer attivo (lo sedchiama spazio modello)
  • b startsi ramificherà incondizionatamente startsull'etichetta che abbiamo creato in precedenza in modo da continuare ad aggiungere la riga successiva purché lo spazio del motivo non contenga bar.
  • /your_regex/pstampa lo spazio del motivo se corrisponde your_regex. È necessario sostituire your_regexl'intera espressione che si desidera abbinare su più righe.

1
+1 Aggiungendo questo al toolikt! Grazie.
wmorrison365,

Nota: su MacOS questo dàsed: 1: "/foo/{:start /bar/!{N;b ...": unexpected EOF (pending }'s)
Stan James il

1
Ottenere sed: unterminated {errore
Nomaed

@Nomaed Shot qui nell'oscurità, ma la tua regex contiene caratteri "{"? In tal caso, dovrai eseguire una backslash-escape.
Joseph R.

1
@Nomaed Sembra che abbia a che fare con le differenze tra le sedimplementazioni. Ho cercato di seguire i consigli in quella risposta per rendere lo script sopra conforme agli standard ma mi ha detto che "start" era un'etichetta indefinita. Quindi non sono sicuro che questo possa essere fatto in modo conforme agli standard. Se lo gestisci, non esitare a modificare la mia risposta.
Joseph R.

19

In genere uso uno strumento chiamato pcregrepche può essere installato nella maggior parte del sapore di Linux usando yumo apt.

Per es.

Supponiamo che tu abbia un file chiamato testfilecon contenuto

abc blah
blah blah
def blah
blah blah

È possibile eseguire il comando seguente:

$ pcregrep -M  'abc.*(\n|.)*def' testfile

per eseguire la corrispondenza dei motivi su più righe.

Inoltre, puoi fare lo stesso anche con sed.

$ sed -e '/abc/,/def/!d' testfile

5

Ecco un approccio più semplice usando Perl:

perl -e '$f=join("",<>); print $& if $f=~/foo\nbar.*\n/m' file

o (da quando JosephR ha preso la sedstrada , ruberò spudoratamente il suo suggerimento )

perl -n000e 'print $& while /^foo.*\nbar.*\n/mg' file

Spiegazione

$f=join("",<>);: questo legge l'intero file e ne salva il contenuto (newline e tutti) nella variabile $f. Quindi tentiamo di abbinare foo\nbar.*\ne stamparlo se corrisponde (la variabile speciale $&contiene l'ultima corrispondenza trovata). Il ///mè necessaria per rendere il match espressione regolare su più righe.

L' -0imposta il separatore di record di input. Impostandolo per 00attivare la 'modalità paragrafo' in cui Perl utilizzerà le nuove righe consecutive ( \n\n) come separatore dei record. Nei casi in cui non ci sono newline consecutive, l'intero file viene letto (bevuto) in una sola volta.

Avvertimento:

Do Non fare questo per file di grandi dimensioni, sarà caricare l'intero file in memoria e che può essere un problema.


2

Un modo per farlo è con Perl. ad esempio, ecco il contenuto di un file chiamato foo:

foo line 1
bar line 2
foo
foo
foo line 5
foo
bar line 6

Ora, ecco un Perl che corrisponderà a qualsiasi linea che inizia con foo seguita da qualsiasi linea che inizia con barra:

cat foo | perl -e 'while(<>){$all .= $_}
  while($all =~ /^(foo[^\n]*\nbar[^\n]*\n)/m) {
  print $1; $all =~ s/^(foo[^\n]*\nbar[^\n]*\n)//m;
}'

Il Perl, suddiviso:

  • while(<>){$all .= $_} Questo carica l'intero input standard nella variabile $all
  • while($all =~Mentre la variabile allha l'espressione regolare ...
  • /^(foo[^\n]*\nbar[^\n]*\n)/mIl regex: foo all'inizio della riga, seguito da un numero qualsiasi di caratteri non newline, seguito da una nuova riga, seguita immediatamente da "barra" e dal resto della riga con la barra in essa. /malla fine del regex significa "abbina più righe"
  • print $1 Stampa la parte della regex che era tra parentesi (in questo caso, l'intera espressione regolare)
  • s/^(foo[^\n]*\nbar[^\n]*\n)//m Cancella la prima corrispondenza per la regex, così possiamo abbinare più casi della regex nel file in questione

E l'output:

foo line 1
bar line 2
foo
bar line 6

3
Sono appena passato per dire che il tuo Perl può essere abbreviato in modo più idiomatico:perl -n0777E 'say $& while /^foo.*\nbar.*\n/mg' foo
Joseph R.

2

Il setaccio alternativo grep supporta la corrispondenza multilinea (dichiarazione di non responsabilità: sono l'autore).

Supponiamo che testfilecontenga:

<Libro>
  <title> Lorem Ipsum </title>
  <description> Lorem ipsum dolor sit amet, consectetur
  adipiscing elit, sed do eiusmod tempo incididunt ut
  labore et dolore magna aliqua </description>
</ Book>


sift -m '<description>.*?</description>' (mostra le righe contenenti la descrizione)

Risultato:

testfile: <description> Lorem ipsum dolor sit amet, consectetur
testfile: adipiscing elit, sed do eiusmod tempo incididunt ut
testfile: labore et dolore magna aliqua </description>


sift -m '<description>(.*?)</description>' --replace 'description="$1"' --no-filename (estrai e riformatta la descrizione)

Risultato:

description = "Lorem ipsum dolor sit amet, consectetur
  adipiscing elit, sed do eiusmod tempo incididunt ut
  labore et dolore magna aliqua "

1
Strumento molto bello. Congratulazioni! Prova a includerlo in distribuzioni come Ubuntu.
Lourenco,

2

Semplicemente un grep normale che supporta i Perl-regexpparametri Pfarà questo lavoro.

$ echo 'abc blah
blah blah
def blah
blah blah' | grep -oPz  '(?s)abc.*?def'
abc blah
blah blah
def

(?s) chiamato modificatore DOTALL che rende punto nella tua regex per abbinare non solo i caratteri ma anche le interruzioni di riga.


Quando provo questa soluzione, l'output non termina con "def" ma va alla fine del file "blah"
buckley,

forse il tuo grep non supporta l' -Popzione
Avinash Raj,

1

Ho risolto questo per me usando grep e l'opzione -A con un altro grep.

grep first_line_word -A 1 testfile | grep second_line_word

L'opzione -A 1 stampa 1 riga dopo la riga trovata. Ovviamente dipende dalla combinazione di file e parole. Ma per me è stata la soluzione più veloce e affidabile.


alias grepp = 'grep --color = auto -B10 -A20 -i' quindi cat somefile | grepp blah | grepp foo | grepp bar ... sì quelli -A e -B sono molto utili ... hai la risposta migliore
Scott Stensland

1

Supponiamo di avere il file test.txt contenente:

blabla
blabla
foo
here
is the
text
to keep between the 2 patterns
bar
blabla
blabla

È possibile utilizzare il seguente codice:

sed -n '/foo/,/bar/p' test.txt

Per il seguente output:

foo
here
is the
text
to keep between the 2 patterns
bar

1

Se vogliamo ottenere il testo tra i 2 pattern escludendoli.

Supponiamo di avere il file test.txt contenente:

blabla
blabla
foo
here
is the
text
to keep between the 2 patterns
bar
blabla
blabla

È possibile utilizzare il seguente codice:

 sed -n '/foo/{
 n
 b gotoloop
 :loop
 N
 :gotoloop
 /bar/!{
 h
 b loop
 }
 /bar/{
 g
 p
 }
 }' test.txt

Per il seguente output:

here
is the
text
to keep between the 2 patterns

Come funziona, facciamolo passo dopo passo

  1. /foo/{ viene attivato quando la riga contiene "pippo"
  2. n sostituire lo spazio del modello con la riga successiva, ovvero la parola "qui"
  3. b gotoloop ramo dell'etichetta "gotoloop"
  4. :gotoloop definisce l'etichetta "gotoloop"
  5. /bar/!{ se il modello non contiene "barra"
  6. h sostituisci lo spazio di attesa con un motivo, quindi "qui" viene salvato nello spazio di attesa
  7. b loop ramo all'etichetta "loop"
  8. :loop definisce l'etichetta "loop"
  9. N accoda il motivo allo spazio di attesa.
    Ora tieni lo spazio contiene:
    "qui"
    "è il"
  10. :gotoloop Siamo ora al passaggio 4 e eseguiamo il ciclo fino a quando una riga contiene "barra"
  11. /bar/ il ciclo è terminato, è stata trovata la "barra", è lo spazio del motivo
  12. g lo spazio del modello viene sostituito con lo spazio di attesa che contiene tutte le linee tra "pippo" e "barra" che sono state salvate durante il ciclo principale
  13. p copia lo spazio del motivo nell'output standard

Fatto !


Ben fatto, +1. Di solito evito di usare questi comandi tracciando le nuove righe in SOH ed eseguendo i normali comandi sed, quindi sostituendo le nuove righe.
A. Danischewski,
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.