web-dev-qa-db-fra.com

Comment puis-je "grep" des modèles sur plusieurs lignes?

Il semble que j'utilise abusivement 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 incorrectes, 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?

30
Jim

Voici un sed qui vous donnera un comportement semblable à grep sur 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 foo et de faire ce qui se trouve à l'intérieur des squigglies avec les lignes correspondantes. Remplacez foo par 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 le contenu des squigglies sur les lignes qui ne correspondent pas à bar. Remplacez bar par la dernière partie du motif.
  • N ajoute la ligne suivante au tampon actif (sed appelle cela l'espace modèle)
  • b start se ramifie inconditionnellement à l'étiquette start 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/p imprime l'espace de motif s'il correspond à your_regex. Vous devez remplacer your_regex par l'expression entière que vous souhaitez faire correspondre sur plusieurs lignes.
29
Joseph R.

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

Par exemple.

Supposons que vous ayez un fichier nommé testfile avec 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 faire de même avec sed.

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

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 route sed , je vais voler sans vergogne son 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.*\n Et de l'imprimer s'il correspond (la variable spéciale $& Contient la dernière correspondance trouvée). Le ///m Est nécessaire pour faire correspondre l'expression régulière entre les nouvelles lignes.

Le -0 Définit le séparateur d'enregistrement d'entrée. La définition de 00 Active 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 pas faites cela pour les fichiers volumineux, cela chargera le fichier entier en mémoire et cela peut être un problème.

6
terdon

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 du Perl qui correspondra à toute 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 .= $_} Ceci charge toute l'entrée standard dans la variable $all
  • while($all =~ Alors que la variable all a l'expression régulière ...
  • /^(foo[^\n]*\nbar[^\n]*\n)/m L'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 contenant une barre . /m À la fin de l'expression régulière signifie "correspondance sur plusieurs lignes"
  • print $1 Imprime 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 Efface 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
samiam

L'alternative grep sift prend en charge la correspondance multiligne (avertissement: je suis l'auteur).

Supposons que testfile contient:

 <book> 
 <title> Lorem Ipsum </title> 
 <description> Lorem ipsum dolor sit amet, consectetur 
 adipiscing elit, sed do eiusmod tempor incididuntunt ut 
 labore et dolore magna aliqua </description> 
 </book> 


sift -m '<description>.*?</description>' (Montrer les lignes contenant la description)

Résultat:

 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 (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" 
2
svent

Simplement un grep normal qui supporte Perl-regexp paramètre P fera 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 en sorte que le point dans votre expression régulière corresponde non seulement aux caractères mais aussi aux sauts de ligne.

2
Avinash Raj

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

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.

1
mansur

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 remplacez l'espace de motif par la ligne suivante, c'est-à-dire le mot "ici"
  3. b gotoloop branche sur le label "gotoloop"
  4. :gotoloop définit le libellé "gotoloop"
  5. /bar/!{ si le motif ne contient pas "bar"
  6. h remplacez l'espace d'attente par un motif, donc "ici" est enregistré dans l'espace d'attente
  7. b loop branchez-vous sur le libellé "boucle"
  8. :loop définit le libellé "boucle"
  9. N ajoute le motif à l'espace d'attente.
    Maintenant, l'espace réservé 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 copie l'espace de motif sur la sortie standard

Terminé !

sedmultiligneboucle

1