Come richiedere il testo in un file e visualizzare il paragrafo che contiene il testo?


24

Di seguito è riportato il testo nel file:

Pseudo name=Apple
Code=42B
state=fault

Pseudo name=Prance
Code=43B
state=good

Ho bisogno di grep per "42B" e ottenere l'output dal testo sopra come:

Pseudo name=Apple
Code=42B
state=fault

Qualcuno ha idea di come raggiungere questo obiettivo utilizzando grep/ awk/ sed?


Hai taggato questa domanda con "grep". Allora cerchi solo soluzioni "grep"? Nella domanda specifichi anche awk & sed. Possiamo aggiungere quei tag? Non ero sicuro del tuo intento quando ho modificato la domanda ieri sera.
slm

Risposte:


38

Con awk

awk -v RS='' '/42B/' file

RS=cambia il separatore del record di input da una nuova riga a righe vuote. Se un campo qualsiasi in un record contiene /42B/stampare il record.

''(la stringa nulla) è un valore magico utilizzato per rappresentare le righe vuote secondo POSIX :

Se RS è nullo, i record sono separati da sequenze costituite da una <newline>o più righe vuote, le righe vuote iniziali o finali non devono generare record vuoti all'inizio o alla fine dell'input e a <newline>deve sempre essere un separatore di campo, non importa quale sia il valore di FS .

I paragrafi di output non verranno separati poiché il separatore di output rimane una nuova riga. Per assicurarsi che tra i paragrafi di output sia presente una riga vuota, impostare il separatore del record di output su due nuove righe:

awk -v RS='' -v ORS='\n\n' '/42B/' file

1
+1 per una soluzione elegante. Non è necessario reindirizzare il file però ...
Jasonwryan,

le dita erano sul pilota automatico.
llua

2
@jasonwryan, a meno che non sia necessario accedere al nome del file all'interno di awk ( FILENAME), non è una cattiva idea utilizzare il reindirizzamento in quanto evita problemi per il nome del file che contiene =o che inizia con -(o essendo -), crea messaggi di errore coerenti ed evita di eseguire awko eseguire altri reindirizzamenti se non è possibile aprire il file di input.
Stéphane Chazelas,

14

Supponendo che i dati siano strutturati in modo tale che sia sempre la riga prima e dopo che si desidera, è possibile utilizzare le opzioni grep -A(after) e -B(before) per indicare che includano 1 riga prima della corrispondenza e 1 riga dopo di essa:

$ grep -A 1 -B 1 "42B" sample.txt
Pseudo name=Apple
Code=42B
state=fault

Se si desidera lo stesso numero di righe prima e dopo il termine di ricerca, è possibile utilizzare l' -Copzione (contesto):

$ grep -C 1 "42B" sample.txt
Pseudo name=Apple
Code=42B
state=fault

Se desideri essere più rigoroso quando abbini più linee puoi usare lo strumento pcregrep, per abbinare un motivo su più linee:

$ pcregrep -M 'Pseudo.*\n.*42B.*\nstate.*' sample.txt
Pseudo name=Apple
Code=42B
state=fault

Il modello sopra corrisponde come segue:

  • -M - più righe
  • 'Pseudo.*\n.*42B.*\nstate.*'- corrisponde a un gruppo di stringhe in cui la prima stringa inizia con la parola "Pseudo"seguita da qualsiasi carattere fino alla fine della riga \n, seguita da qualsiasi carattere fino alla stringa "42B"seguita da qualsiasi carattere fino alla fine della riga ( \n), seguita dalla stringa "state"seguito da qualsiasi personaggio.

5
-C(contesto) può essere usato come scorciatoia, se -Ae -Bsono uguali.
David Baggerman,

@DavidBaggerman - grazie. Aggiunto alla risposta.
slm

Perché l'uno vota? Questo risponde alla domanda.
slm

4

C'è probabilmente un modo altrettanto semplice per farlo con awk, ma in perl:

cat file | perl -ne 'BEGIN { $/="\n\n" }; print if $_ =~ /42B/;'

Questo in sostanza dice di dividere il file in blocchi delimitati da righe vuote, quindi di stampare solo quei blocchi che corrispondono alla tua espressione regolare.


10
Questo può essere semplificato usando opzioni e shorthands e perdendo l' uso inutile dicat ; perl -00 -ne 'print if /42B/' file
triplo

4

Il grepdi alcuni tipi di Unix hanno la -pbandierina per "paragrafo". So che AIX lo fa .

grep -p 42B <myfile>

farebbe esattamente quello che stai chiedendo lì. YMMV e GNU grep non hanno questo flag.


Avere la bandiera -p sarebbe meraviglioso. Soprattutto se usato insieme a -v in modo da poter escludere interi paragrafi dall'output.
IllvilJa

2

Un'altra soluzione perl, senza una riga vuota finale:

perl -00ne 'if ($_ =~ /42B/) {chomp($_); printf "%s\n",$_}' foo

Esempio

% perl -00ne 'if ($_ =~ /42B/) {chomp($_); printf "%s\n",$_}' foo
Pseudo name=Apple
Code=42B
state=fault

% cat foo
Pseudo name=Apple
Code=42B
state=fault

Pseudo name=Prance
Code=43B
state=good

1
O più brevi (e quindi più leggibile), come triplee ha scritto in un commento: perl -00 -ne 'print if /42B/' file.
mivk
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.