Comment puis-je «grep» des motifs sur plusieurs lignes?


24

Il semble que j'abuse grep/ egrep.

J'essayais de rechercher des chaînes sur plusieurs lignes et je n'ai pas trouvé de correspondance alors que je sais que ce que je recherche doit correspondre. À l'origine, je pensais que mes expressions rationnelles étaient erronées, mais j'ai finalement lu que ces outils fonctionnent par ligne (également mes expressions régulières étaient si triviales que cela ne pouvait pas être le problème).

Alors, quel outil utiliserait-on pour rechercher des modèles sur plusieurs lignes?



1
@CiroSantilli - Je ne pense pas que ce Q et celui auquel vous avez lié sont des doublons. L'autre Q demande comment vous feriez une correspondance de modèle sur plusieurs lignes (c'est-à-dire avec quel outil dois-je / puis-je utiliser pour le faire) tandis que celui-ci demande comment le faire grep. Ils sont étroitement liés mais pas dupes, OMI.
slm

@sim ces cas sont difficiles à décider: je peux voir votre point. Je pense que ce cas particulier est préférable en tant que doublon parce que l'utilisateur a dit de "grep"suggérer le verbe "à grep", et les meilleures réponses, y compris acceptées, n'utilisent pas grep.
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Réponses:


24

En voici sedun qui vous donnera un grepcomportement semblable à plusieurs lignes:

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

Comment ça marche

  • -n supprime le comportement par défaut de l'impression de chaque ligne
  • /foo/{}lui demande de faire correspondre fooet de faire ce qui vient à l'intérieur des gribouillis aux lignes correspondantes. Remplacez foopar la partie de départ du motif.
  • :start est une étiquette de branchement pour nous aider à continuer à boucler jusqu'à ce que nous trouvions la fin de notre expression régulière.
  • /bar/!{}exécutera ce qui est dans les gribouillis sur les lignes qui ne correspondent pas bar. Remplacez barpar la partie finale du motif.
  • Najoute la ligne suivante au tampon actif ( sedappelle cela l'espace de motif)
  • b startse ramifiera inconditionnellement à l' startétiquette que nous avons créée plus tôt afin de continuer à ajouter la ligne suivante tant que l'espace de motif ne contient pas bar.
  • /your_regex/pimprime l'espace de motif s'il correspond your_regex. Vous devez remplacer your_regexpar l'expression entière que vous souhaitez faire correspondre sur plusieurs lignes.

1
+1 Ajout de cela à l'outil! Merci.
wmorrison365

Remarque: sur MacOS, cela donnesed: 1: "/foo/{:start /bar/!{N;b ...": unexpected EOF (pending }'s)
Stan James

1
Obtenir sed: unterminated {erreur
Nomaed

@Nomaed Shot dans le noir ici, mais votre expression régulière contient-elle des caractères "{"? Si tel est le cas, vous devrez les annuler pour les échapper.
Joseph R.

1
@Nomaed Il semble que cela ait à voir avec les différences entre les sedimplémentations. J'ai essayé de suivre les recommandations de cette réponse pour rendre le script ci-dessus conforme aux normes, mais il m'a dit que "démarrer" était une étiquette non définie. Je ne sais donc pas si cela peut être fait de manière conforme aux normes. Si vous y parvenez, n'hésitez pas à modifier ma réponse.
Joseph R.

19

J'utilise généralement un outil appelé pcregrepqui peut être installé dans la plupart des versions linux en utilisant yumou apt.

Par exemple.

Supposons que vous ayez un fichier nommé testfileavec du contenu

abc blah
blah blah
def blah
blah blah

Vous pouvez exécuter la commande suivante:

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

pour faire correspondre les modèles sur plusieurs lignes.

De plus, vous pouvez également faire de même avec sed.

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

5

Voici une approche plus simple en utilisant Perl:

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

ou (puisque JosephR a pris la sedroute , je vais voler sans vergogne sa suggestion )

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

Explication

$f=join("",<>);: ceci lit le fichier entier et enregistre son contenu (sauts de ligne et tout) dans la variable $f. Nous essayons ensuite de faire correspondre foo\nbar.*\net de l'imprimer s'il correspond (la variable spéciale $&contient la dernière correspondance trouvée). Le ///mest nécessaire pour faire correspondre l'expression régulière entre les nouvelles lignes.

Le -0définit le séparateur d'enregistrement d'entrée. La définition de ce paramètre 00active le «mode paragraphe» où Perl utilisera des sauts de ligne consécutifs ( \n\n) comme séparateur d'enregistrement. Dans les cas où il n'y a pas de sauts de ligne consécutifs, le fichier entier est lu (slurped) à la fois.

Attention:

Ne faites pas cela pour les fichiers volumineux, cela chargera le fichier entier en mémoire et cela peut être un problème.


2

Une façon de le faire est avec Perl. par exemple, voici le contenu d'un fichier nommé foo:

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

Maintenant, voici quelques Perl qui correspondront à n'importe quelle ligne commençant par foo suivie par toute ligne commençant par 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;
}'

Le Perl, décomposé:

  • while(<>){$all .= $_} Cela charge l'intégralité de l'entrée standard dans la variable $all
  • while($all =~Alors que la variable alla l'expression régulière ...
  • /^(foo[^\n]*\nbar[^\n]*\n)/mL'expression régulière: foo au début de la ligne, suivie d'un nombre quelconque de caractères non-newline, suivie d'une nouvelle ligne, suivie immédiatement de "bar" et du reste de la ligne avec barre. /mà la fin de l'expression régulière signifie "correspondance sur plusieurs lignes"
  • print $1 Imprimer la partie de l'expression régulière qui était entre parenthèses (dans ce cas, l'expression régulière entière)
  • s/^(foo[^\n]*\nbar[^\n]*\n)//m Effacer la première correspondance pour l'expression régulière, afin que nous puissions faire correspondre plusieurs cas de l'expression régulière dans le fichier en question

Et la sortie:

foo line 1
bar line 2
foo
bar line 6

3
Je viens juste de dire que votre Perl peut être raccourci vers le plus idiomatique:perl -n0777E 'say $& while /^foo.*\nbar.*\n/mg' foo
Joseph R.

2

La solution grep EIPD soutient correspondant multiligne (disclaimer: je suis l'auteur).

Supposons que testfilecontient:

<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>' (montrer les lignes contenant la description)

Résultat:

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


sift -m '<description>(.*?)</description>' --replace 'description="$1"' --no-filename (extraire et reformater la description)

Résultat:

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

1
Très bel outil. Toutes nos félicitations! Essayez de l'inclure dans des distributions comme Ubuntu.
Lourenco

2

Un simple grep normal qui prend en charge le Perl-regexpparamètre Pfera ce travail.

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

(?s) appelé modificateur DOTALL qui fait que les points de votre expression régulière correspondent non seulement aux caractères mais aussi aux sauts de ligne.


Lorsque j'essaie cette solution, la sortie ne se termine pas à «def» mais va à la fin du fichier «blah»
buckley

peut-être que votre grep ne prend pas en charge l' -Poption
Avinash Raj

1

J'ai résolu celui-ci pour moi en utilisant grep et l'option -A avec un autre grep.

grep first_line_word -A 1 testfile | grep second_line_word

L'option -A 1 imprime 1 ligne après la ligne trouvée. Bien sûr, cela dépend de votre combinaison de fichiers et de mots. Mais pour moi, c'était la solution la plus rapide et la plus fiable.


alias grepp = 'grep --color = auto -B10 -A20 -i' puis cat somefile | grepp blah | grepp foo | grepp bar ... oui ceux -A et -B sont très pratiques ... vous avez la meilleure réponse
Scott Stensland

1

Supposons que nous ayons le fichier test.txt contenant:

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

Le code suivant peut être utilisé:

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

Pour la sortie suivante:

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

1

Si nous voulons obtenir le texte entre les 2 motifs en s'excluant.

Supposons que nous ayons le fichier test.txt contenant:

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

Le code suivant peut être utilisé:

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

Pour la sortie suivante:

here
is the
text
to keep between the 2 patterns

Comment ça marche, faisons-le pas à pas

  1. /foo/{ est déclenché lorsque la ligne contient "foo"
  2. n remplacer l'espace de motif par la ligne suivante, c'est-à-dire le mot "ici"
  3. b gotoloop branchement au label "gotoloop"
  4. :gotoloop définit le label "gotoloop"
  5. /bar/!{ si le motif ne contient pas "bar"
  6. h remplacer l'espace de maintien par un motif, donc "ici" est enregistré dans l'espace de maintien
  7. b loop branche au label "boucle"
  8. :loop définit l'étiquette "boucle"
  9. N ajoute le motif à l'espace d'attente.
    Maintenant, l'espace de stockage contient:
    "ici"
    "est le"
  10. :gotoloop Nous sommes maintenant à l'étape 4, et bouclons jusqu'à ce qu'une ligne contienne "bar"
  11. /bar/ la boucle est terminée, "bar" a été trouvé, c'est l'espace du motif
  12. g l'espace de motif est remplacé par un espace d'attente qui contient toutes les lignes entre "foo" et "bar" qui ont été enregistrées pendant la boucle principale
  13. p copier l'espace du motif sur la sortie standard

Terminé !


Bravo, +1. J'évite généralement d'utiliser ces commandes en tr'ant les sauts de ligne dans SOH et en exécutant les commandes sed normales puis en remplaçant les sauts de ligne.
A.Danischewski
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.