248 votes

Comment trouver des motifs sur plusieurs lignes en utilisant grep ?

Je veux trouver les fichiers qui contiennent "abc" ET "efg" dans cet ordre, et ces deux chaînes de caractères sont sur des lignes différentes dans ce fichier. Par exemple, un fichier avec un contenu :

blah blah..
blah blah..
blah abc blah
blah blah..
blah blah..
blah blah..
blah efg blah blah
blah blah..
blah blah..

Il devrait être assorti.

4 votes

1 votes

En fait, dans notre monde, rien ne reste inchangé pendant un certain temps. Donc il y aura peut-être de meilleurs fils que celui-ci quelque part dans le futur.

258voto

ring bearer Points 8369

Grep n'est pas suffisant pour cette opération.

pcregrep que l'on trouve dans la plupart des systèmes Linux modernes, peut être utilisé en tant que

pcregrep -M  'abc.*(\n|.)*efg' test.txt

-M , --multiline permettre aux motifs de correspondre à plus d'une ligne

Il y a un plus récent pcre2grep également. Les deux sont fournis par le Projet PCRE .

pcre2grep est disponible pour Mac OS X via Ports Mac dans le cadre du port pcre2 :

% sudo port install pcre2 

et par Homebrew comme :

% brew install pcre

ou pour pcre2

% brew install pcre2

pcre2grep est également disponible sur Linux (Ubuntu 18.04+)

$ sudo apt install pcre2-utils # PCRE2
$ sudo apt install pcregrep    # Older PCRE

11 votes

@StevenLu -M, --multiline - Permettre aux motifs de correspondre à plus d'une ligne.

7 votes

Notez que .*( \n |.)* est équivalent à ( \n |.)* et ce dernier est plus court. De plus, sur mon système, "pcre_exec() error -8" se produit lorsque j'exécute la version la plus longue. Essayez donc 'abc( \n |.)*efg' à la place !

0 votes

Cela correspondait pour moi, à partir de la ligne avec abc à la dernière ligne avec efg . comment puis-je lui dire de s'arrêter à la PREMIÈRE occurrence de efg ?

122voto

LJ. Points 611

Je ne suis pas sûr que ce soit possible avec grep, mais sed le rend très facile :

sed -e '/abc/,/efg/!d' [file-with-content]

4 votes

Cela ne trouve pas les fichiers, cela renvoie la partie correspondante d'un seul fichier.

13 votes

@Lj. Pouvez-vous expliquer cette commande ? Je suis familier avec sed mais je n'ai jamais vu une telle expression auparavant.

2 votes

@Anthony, C'est documenté dans la page de manuel de sed, sous adresse. Il est important de réaliser que /abc/ & /efg/ est une adresse.

117voto

atti Points 11

Voici une solution inspirée par cette réponse :

  • si 'abc' et 'efg' peuvent être sur la même ligne :

      grep -zl 'abc.*efg' <your list of files>
  • si 'abc' et 'efg' doivent être sur des lignes différentes :

      grep -Pzl '(?s)abc.*\n.*efg' <your list of files>

Params :

  • -P Utilisez des expressions régulières compatibles avec Perl (PCRE).

  • -z Traiter l'entrée comme un ensemble de lignes, chacune terminée par un octet zéro au lieu d'une nouvelle ligne. c'est-à-dire que grep traite l'entrée comme une seule grande ligne.

  • -l liste des noms de fichiers correspondants uniquement.

  • (?s) active PCRE_DOTALL, ce qui signifie que '.' trouve n'importe quel caractère ou nouvelle ligne.

0 votes

@syntaxerror Non, je pense que c'est juste une minuscule. l . AFAIK il n'y a pas de numéro -1 option.

0 votes

Il semble que vous ayez raison après tout, j'ai peut-être fait une faute de frappe lors du test. Dans tous les cas, désolé d'avoir laissé une fausse piste.

9 votes

C'est excellent. J'ai juste une question à ce sujet. Si le -z options spécifie que grep doit traiter les nouvelles lignes comme zero byte characters alors pourquoi avons-nous besoin de la (?s) dans la regex ? Si c'est déjà un caractère qui n'est pas une nouvelle ligne, est-ce que . être en mesure d'y correspondre directement ?

34voto

A.Danischewski Points 53

Sed devrait suffire comme l'a indiqué poster LJ ci-dessus,

au lieu de !d, vous pouvez simplement utiliser p pour imprimer :

sed -n '/abc/,/efg/p' file

6voto

sundar Points 2271

Vous pouvez le faire très facilement si vous savez utiliser Perl.

perl -ne 'if (/abc/) { $abc = 1; next }; print "Found in $ARGV\n" if ($abc && /efg/); }' yourfilename.txt

Vous pouvez aussi le faire avec une seule expression régulière, mais cela implique de prendre tout le contenu du fichier dans une seule chaîne, ce qui peut finir par prendre trop de mémoire avec les gros fichiers. Pour être complet, voici cette méthode :

perl -e '@lines = <>; $content = join("", @lines); print "Found in $ARGV\n" if ($content =~ /abc.*efg/s);' yourfilename.txt

0 votes

J'ai trouvé que la deuxième réponse était utile pour extraire un bloc entier de plusieurs lignes avec des correspondances sur quelques lignes - j'ai dû utiliser une correspondance non avide ( .*? ) pour obtenir une correspondance minimale.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X