web-dev-qa-db-fra.com

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

Pour être précis

Some text
begin
Some text goes here.
end
Some more text

et je veux extraire tout le bloc qui commence par "begin" jusqu'à "end".

avec awk nous pouvons faire comme awk '/begin/,/end/' text.

Comment faire avec grep?

18
Iker

Mise à jour le 18 nov. 2016 (le comportement de grep ayant été modifié: grep avec le paramètre -P ne prend désormais plus en charge les ancres ^ et $ [sous Ubuntu 16.04 avec noyau v: 4.4.0-21-generic]) ( correctif (non) correct) )

$ grep -Pzo "begin(.|\n)*\nend" file
begin
Some text goes here.  
end

remarque: pour les autres commandes, il suffit de remplacer les ancres '^' et '$' par des ancres new-line '\n' ______________________________

Avec la commande grep:

grep -Pzo "^begin\$(.|\n)*^end$" file

Si vous souhaitez ne pas inclure les modèles "begin" et "end" dans le résultat, utilisez grep avec le support Lookbehind et Lookahead.

grep -Pzo "(?<=^begin$\n)(.|\n)*(?=\n^end$)" file

Vous pouvez également utiliser \K notify au lieu de l'assertion Lookbehind.

grep -Pzo "^begin$\n\K(.|\n)*(?=\n^end$)" file

L'option \K ignore tout ce qui se trouve avant le filtrage du motif et ignore le motif lui-même.
\n utilisé pour éviter d’imprimer des lignes vides à partir de la sortie.

Ou, comme @AvinashRaj le suggère, il existe de simples grep faciles comme suit:

grep -Pzo "(?s)^begin$.*?^end$" file

grep -Pzo "^begin\$[\s\S]*?^end$" file

(?s) indique à grep d'autoriser le point à correspondre aux caractères de nouvelle ligne.
[\s\S] correspond à tout caractère blanc ou non.

Et leur sortie sans inclure "begin" et "end" est la suivante:

grep -Pzo "^begin$\n\K[\s\S]*?(?=\n^end$)" file # or grep -Pzo "(?<=^begin$\n)[\s\S]*?(?=\n^end$)"

grep -Pzo "(?s)(?<=^begin$\n).*?(?=\n^end$)" file

voir le test complet de toutes les commandes ici (le comportement de grep avec le paramètre -P n'est plus daté)

Remarque:

^ pointe le début d'une ligne et $ pointe la fin d'une ligne. ceux-ci sont ajoutés aux environs de "commencer" et "fin" pour les faire correspondre s'ils sont seuls dans une ligne.
Dans deux commandes, j’ai échappé à $ car il utilise également pour "Substitution de commande" ($(command)) qui permet à la sortie d’une commande de remplacer le nom de la commande.

De l'homme grep:

-o, --only-matching
      Print only the matched (non-empty) parts of a matching line,
      with each such part on a separate output line.

-P, --Perl-regexp
      Interpret PATTERN as a Perl compatible regular expression (PCRE)

-z, --null-data
      Treat the input as a set of lines, each terminated by a zero byte (the ASCII 
      NUL character) instead of a newline. Like the -Z or --null option, this option 
      can be used with commands like sort -z to process arbitrary file names.
13
αғsнιη

Si votre grep ne prend pas en charge la syntaxe Perl (-P), vous pouvez essayer de joindre les lignes, en faisant correspondre le modèle, puis en les développant à nouveau comme suit:

$ tr '\n' , < foo.txt | grep -o "begin.*end" | tr , '\n'
begin
Some text goes here.
end
2
kenorb