Comment chercher du texte dans un fichier et afficher le paragraphe qui contient le texte?


24

Voici le texte du fichier:

Pseudo name=Apple
Code=42B
state=fault

Pseudo name=Prance
Code=43B
state=good

J'ai besoin de grep pour "42B" et d'obtenir la sortie du texte ci-dessus comme:

Pseudo name=Apple
Code=42B
state=fault

Quelqu'un a-t-il une idée sur la façon d'y parvenir en utilisant grep/ awk/ sed?


Vous avez marqué cette question avec simplement "grep". Cherchez-vous seulement des solutions "grep" alors? Dans la question, vous spécifiez également awk & sed. Pouvons-nous ajouter ces balises? Je n'étais pas sûr de votre intention lorsque j'ai modifié la question hier soir.
slm

Réponses:


38

Avec awk

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

RS=change le séparateur d'enregistrement d'entrée d'une nouvelle ligne en lignes vides. Si un champ d'un enregistrement contient /42B/imprimer l'enregistrement.

''(la chaîne nulle) est une valeur magique utilisée pour représenter les lignes vides selon POSIX :

Si RS est nul, les enregistrements sont séparés par des séquences composées de <newline>plus une ou plusieurs lignes vides, les lignes vides de début ou de fin ne doivent pas entraîner des enregistrements vides au début ou à la fin de l'entrée, et a <newline>doit toujours être un séparateur de champ, quelle que soit la valeur de FS .

Les paragraphes de sortie ne seront pas séparés car le séparateur de sortie reste une seule nouvelle ligne. Pour vous assurer qu'il y a une ligne vide entre les paragraphes de sortie, définissez le séparateur d'enregistrement de sortie sur deux retours à la ligne:

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

1
+1 pour une solution élégante. Mais vous n'avez pas besoin de rediriger le fichier ...
jasonwryan

les doigts étaient sur le pilote automatique.
llua

2
@jasonwryan, sauf si vous avez besoin d'accéder au nom de fichier dans awk ( FILENAME), ce n'est pas une mauvaise idée d'utiliser la redirection car cela évite les problèmes de nom de fichier contenant =ou commençant par -(ou étant -), crée des messages d'erreur cohérents et évite d'exécuter awkou d'exécuter d'autres redirections si le fichier d'entrée ne peut pas être ouvert.
Stéphane Chazelas

14

En supposant que les données sont structurées de sorte que ce soit toujours la ligne avant et après que vous voulez, vous pouvez utiliser les commutateurs de grep -A(après) et -B(avant) pour lui dire d'inclure la 1 ligne avant la correspondance et 1 ligne après:

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

Si vous voulez les mêmes lignes numériques avant et après le terme de recherche, vous pouvez utiliser le -Ccommutateur (context):

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

Si vous souhaitez être plus rigoureux lors de la correspondance des plusieurs lignes, vous pouvez utiliser l'outil pcregrep, pour faire correspondre un modèle sur plusieurs lignes:

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

Le modèle ci-dessus correspond comme suit:

  • -M - plusieurs lignes
  • 'Pseudo.*\n.*42B.*\nstate.*'- correspond à un groupe de chaînes où la première chaîne commence par le mot "Pseudo"suivi de tout caractère jusqu'à la fin de la ligne \n, suivi de tout caractère jusqu'à la chaîne "42B"suivi de tout caractère jusqu'à une autre fin de ligne ( \n), suivi de la chaîne "state"suivi de tous les caractères.

5
-C(contexte) peut être utilisé comme raccourci, si -Aet -Bsont les mêmes.
David Baggerman

@DavidBaggerman - merci. Ajouté à la réponse.
slm

Pourquoi le vote unique? Cela répond à la question.
slm

4

Il existe probablement un moyen similaire de le faire avec awk, mais en perl:

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

Cela dit essentiellement de diviser le fichier en morceaux délimités par des lignes vides, puis d'imprimer uniquement les morceaux qui correspondent à votre expression régulière.


10
Cela peut être simplifié en utilisant des options et des raccourcis, et en perdant l' utilisation inutile decat ; perl -00 -ne 'print if /42B/' file
tripleee

4

La grepde quelques saveurs d'Unix ont le -pdrapeau pour « paragraphe ». Je sais qu'AIX le fait .

grep -p 42B <myfile>

ferait exactement ce que vous demandez là-bas. YMMV et GNU grep n'ont pas cet indicateur.


Avoir le drapeau -p serait merveilleux. Surtout si utilisé avec -v, vous pouvez exclure des paragraphes entiers de la sortie.
IllvilJa

2

Une autre solution Perl, sans ligne vide de fin:

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

Exemple

% 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
Ou plus court (et donc plus lisible), comme Triplee a écrit dans un commentaire: perl -00 -ne 'print if /42B/' file.
mivk
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.