Sembra che io stia utilizzando in modo improprio 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. Allinizio pensavo che le mie regex fossero sbagliate ma alla fine ho letto che queste gli strumenti funzionano per riga (anche le mie espressioni regolari erano così banali che non poteva essere il problema).
Quindi quale strumento si usa per cercare pattern su più righe?
Commenti
Risposta
Qui “sa sed
uno che ti darà un comportamento simile a grep
su più righe:
sed -n "/foo/{:start /bar/!{N;b start};/your_regex/p}" your_file
Come funziona
-
-n
sopprime il comportamento predefinito di stampa di ogni riga -
/foo/{}
indica di corrispondere afoo
e fai ciò che si trova allinterno degli squiggli alle linee corrispondenti. Sostituiscifoo
con la parte iniziale del pattern. -
:start
è unetichetta di ramificazione che ci aiuta a continuare il ciclo finché non troviamo la fine della nostra regex. -
/bar/!{}
eseguirà ciò che “s negli squigglies per le righe che non corrispondono abar
. Sostituisci con la parte finale del pattern. -
N
aggiunge la riga successiva al buffer attivo (sed
lo chiama lo spazio del modello) -
b start
si diramerà incondizionatamente alletichettastart
che abbiamo creato in precedenza in modo da continuare ad aggiungere la riga successiva fintanto che lo spazio del pattern non “t contienebar
. -
/your_regex/p
stampa lo spazio del pattern se corrisponde ayour_regex
. È necessario sostituireyour_regex
con lintera espressione che si desidera trovare su più righe.
Commenti
- +1 Aggiungendo questo al toolikt! Grazie.
- Nota. Su MacOS questo dà
sed: 1: "/foo/{:start /bar/!{N;b ...": unexpected EOF (pending }'s)
- Ricezione di
sed: unterminated {
errore - @Nomaed Shot in the dark here, ma la tua espressione regolare contiene caratteri ” {“? In tal caso, ‘ dovrai eseguire lescape con la barra rovesciata.
- @Nomaed Sembra che abbia a che fare con le differenze tra le
sed
implementazioni. Ho provato a seguire i consigli in quella risposta per rendere lo script precedente conforme allo standard, ma mi è stato detto che ” start ” era undefined etichetta. Quindi ‘ non sono sicuro che ciò possa essere fatto in modo conforme agli standard. Se riesci a gestirlo, non esitare a modificare la mia risposta.
Risposta
In genere utilizzo uno strumento chiamato pcregrep
che può essere installato nella maggior parte delle versioni di Linux utilizzando yum
o apt
.
Ad esempio.
Supponi di avere un file denominato testfile
con contenuto
abc blah blah blah def blah blah blah
Puoi eseguire il seguente comando:
$ pcregrep -M "abc.*(\n|.)*def" testfile
per eseguire la corrispondenza di modelli su più righe.
Inoltre, puoi fare lo stesso anche con sed
.
$ sed -e "/abc/,/def/!d" testfile
Commenti
- questo
sed
suggerimento salta la riga in cui si trovadef
Risposta
Semplicemente un normale grep che supporta il Perl-regexp
parametro P
farà 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 fa sì che il punto nella tua espressione regolare corrisponda non solo ai caratteri ma anche alle interruzioni di riga.
Commenti
- Quando provo questa soluzione loutput non termina con ‘ def ‘ ma va alla fine del file ‘ blah ‘
- forse il tuo grep non supporta lopzione
-P
- Questa era lunica che ha funzionato per me – ho provato tutti i
sed
suggerimenti, ma ‘ non è arrivato fino allinstallazione di alternative grep. -
$ grep --version
:grep (GNU grep) 3.1
in Windows Git Bash ha unopzione-P, --perl-regexp
ma(?s)
non ‘ sembra che non funzioni lì. Mostra ancora solo la prima riga. Lo stesso pattern con la stessa stringa di test funziona su regex101.com . Esiste unalternativa in Git Bash?sed
? (sed (GNU sed) 4.8
qui) - Sai come aggiungere il contesto alloutput? grep -1 non ‘ funziona qui.
Risposta
Ecco “un approccio più semplice utilizzando Perl:
perl -e "$f=join("",<>); print $& if $f=~/foo\nbar.*\n/m" file
o (poiché JosephR ha preso il sed
route , ruberò spudoratamente il suo suggerimento )
perl -n000e "print $& while /^foo.*\nbar.*\n/mg" file
### Spiegazione
$f=join("",<>);
: questo legge lintero file e salva il suo contenuto (nuove righe e tutto) nella variabile $f
. Tentiamo quindi di trovare una corrispondenza con foo\nbar.*\n
e di stamparla se corrisponde (la variabile speciale $&
contiene lultima corrispondenza trovata). ///m
è necessario per fare in modo che lespressione regolare corrisponda tra le nuove righe.
-0
imposta il separatore del record di input. Impostando questo a 00
si attiva la “modalità paragrafo” dove Perl userà le nuove righe consecutive (\n\n
) come separatore di record. Nei casi in cui non ci sono nuove righe consecutive, lintero file viene letto (slurpato) in una volta.
### Attenzione: non farlo per file di grandi dimensioni, verrà caricato lintero file in memoria e questo potrebbe essere un problema.
Commenti
- Non ‘ t so molto di Perl, ma ‘ t deve essere
my $f=join("",<>);
, in senso stretto? - solo @Sapphire_Brick se sei in modalità rigorosa (
use strict;
). ‘ è una buona abitudine da prendere, soprattutto quando si scrivono script più grandi, ma ‘ è troppo faticoso per una battuta come questa uno.
Risposta
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
Risposta
Lalternativa grep sift supporta la corrispondenza multilinea (disclaimer: io sono lautore).
Supponi testfile
contiene:
<book> <title>Lorem Ipsum</title> <description>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua</description> </book>
sift -m '<description>.*?</description>'
(mostra le righe che contengono la descrizione)
Risultato:
testfile: <description>Lorem ipsum dolor sit amet, consectetur testfile: adipiscing elit, sed do eiusmod tempor incididunt ut testfile: labore et dolore magna aliqua</description>
sift -m '<description>(.*?)</description>' --replace 'description="$1"' --no-filename
(estrae e riformattare la descrizione)
Risultato:
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
Commenti
- Strumento molto carino. Congratulazioni! Prova a includerlo in distribuzioni come Ubuntu.
Risposta
Ho risolto questo per me usando grep e – Unopzione con un altro grep.
grep first_line_word -A 1 testfile | grep second_line_word
Lopzione -A 1 stampa 1 riga dopo la riga trovata. Ovviamente dipende dal file e dalla combinazione di parole. Ma per me era la soluzione più veloce e affidabile.
Commenti
- 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
- Questo non è ‘ t super deterministico e ignora lintero schema a favore di ottenere solo una singola riga diversa (basata solo sulla sua prossimità alla prima riga). ‘ è meglio dire al programma di spingersi oltre il limite necessario per arrivare a una sorta di schema che ‘ re assolutamente certo è la fine del testo che ‘ stai cercando di abbinare. Ad esempio, se
testfile
viene aggiornato in modo tale chesecond_line_word
si trovi sulla terza riga, non solo ti manca la prima riga (a causa di il secondogrep
) ma ‘ non manca la riga che ha iniziato a comparire tra i due. - Questo sarebbe un MO abbastanza buono per comandi ad hoc in cui vuoi davvero solo una singola riga in output che hai già capito. Non ‘ penso che ‘ sia ciò che sta cercando lOP e probabilmente potresti anche copiare / incollare a quel punto a causa di essendo ad hoc.
Risposta
Un modo per farlo è con Perl. per esempio. ecco il contenuto di un file denominato foo
:
foo line 1 bar line 2 foo foo foo line 5 foo bar line 6
Ora, ecco un po di Perl che corrisponde a qualsiasi riga che inizia con foo seguita da qualsiasi riga che inizia con bar:
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 lintero standard input nella variabile$all
-
while($all =~
Mentre la variabileall
ha lespressione regolare … -
/^(foo[^\n]*\nbar[^\n]*\n)/m
Lespressione regolare: foo allinizio della riga, seguito da un numero qualsiasi di caratteri non di nuova riga, seguito da una nuova riga, seguita immediatamente da “bar” e dal resto della riga contenente una barra./m
alla fine della regex significa “corrispondenza su più righe” -
print $1
Stampa la parte della regex che era tra parentesi (in questo caso, lintera espressione regolare) -
s/^(foo[^\n]*\nbar[^\n]*\n)//m
Cancella la prima corrispondenza per la regex, così possiamo trovare più casi della regex nel file in questione
E loutput:
foo line 1 bar line 2 foo bar line 6
Commenti
- Sono appena passato per dire che il tuo Perl può essere abbreviato nel modo più idiomatico:
perl -n0777E 'say $& while /^foo.*\nbar.*\n/mg' foo
Risposta
Se vogliamo che il testo tra i 2 pattern si escluda.
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 codice seguente:
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, let “s passo dopo passo
-
/foo/{
viene attivato quando la riga contiene “foo” -
n
sostituisci lo spazio del pattern con la riga successiva, cioè la parola “qui” -
b gotoloop
ramo con letichetta “gotoloop” -
:gotoloop
definisce letichetta “gotoloop” -
/bar/!{
se il pattern non “t contiene” bar “ -
h
sostituisci lo spazio di conservazione con il motivo, quindi “here” viene salvato nello spazio di conservazione -
b loop
si dirama alletichetta “loop” -
:loop
definisce letichetta “loop” -
N
aggiunge il pattern allo spazio di blocco.
Ora lo spazio di blocco contiene:
“here”
“è” -
:gotoloop
Siamo ora al passaggio 4 e ripetiamo il ciclo finché una riga non contiene “bar” -
/bar/
il ciclo è terminato, “bar” è stato trovato, it ” è lo spazio del pattern - lo spazio del pattern viene sostituito con lo spazio di attesa che contiene tutte le linee tra” foo “e” bar “che sono state salvate durante il ciclo principale
-
p
copia lo spazio del modello nelloutput standard
Fatto!
Commenti
- Ben fatto, +1. Di solito evito di usare questi comandi tr ‘ inserendo le nuove righe in SOH ed eseguendo normali comandi sed, quindi sostituendo le nuove righe.
grep
. Sono strettamente correlati ma non doppi, IMO."grep"
suggerendo il verbo ” a grep ” e le risposte migliori, includendo accettato, don ‘ t usa grep.