web-dev-qa-db-fra.com

Comment trouver des modèles sur plusieurs lignes à l'aide de grep?

Je veux trouver des fichiers qui ont "abc" ET "efg" dans cet ordre et ces deux chaînes sont sur des lignes différentes dans ce fichier 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..

Devrait être assorti.

171
Saobi

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

pcregrep , présent dans la plupart des systèmes Linux modernes, peut être utilisé comme

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

Il y a un nouveau 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 via Homebrew comme:

% brew install pcre

ou pour pcre2

% brew install pcre2
185
ring bearer

Je ne suis pas sûr que ce soit possible avec grep, mais sed facilite grandement les choses:

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

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 figurer sur des lignes différentes:

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

Paramètres:

  • -z Traite l'entrée comme un ensemble de lignes, chacune terminée par un octet nul au lieu d'une nouvelle ligne. c’est-à-dire que grep menace l’entrée sous la forme d’une ligne unique.

  • -l Nom de chaque fichier d'entrée à partir duquel la sortie aurait normalement été imprimée.

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

65
atti

sed devrait suffire comme le dit l'affiche LJ ci-dessus, 

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

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

Je me suis beaucoup appuyé sur pcregrep, mais avec le nouveau grep, vous n'avez pas besoin d'installer pcregrep pour la plupart de ses fonctionnalités. Il suffit d'utiliser grep -P.

Dans l'exemple de la question du PO, je pense que les options suivantes fonctionnent bien, la deuxième meilleure correspondance permettant de comprendre la question:

grep -Pzo "abc(.|\n)*efg" /tmp/tes*
grep -Pzl "abc(.|\n)*efg" /tmp/tes*

J'ai copié le texte en tant que/tmp/test1 et supprimé le «g» et enregistré en tant que/tmp/test2. Voici le résultat montrant que le premier affiche la chaîne correspondante et que le second affiche uniquement le nom du fichier (typiquement -o indique la correspondance et typique -l indique uniquement le nom du fichier). Notez que le "z" est nécessaire pour multiligne et que le "(. |\N)" signifie "tout autre que nouvelle ligne" ou "nouvelle ligne", c’est-à-dire:

user@Host:~$ grep -Pzo "abc(.|\n)*efg" /tmp/tes*
/tmp/test1:abc blah
blah blah..
blah blah..
blah blah..
blah efg
user@Host:~$ grep -Pzl "abc(.|\n)*efg" /tmp/tes*
/tmp/test1

Pour déterminer si votre version est suffisamment nouvelle, exécutez man grep et voyez si quelque chose de similaire apparaît dans la partie supérieure:

   -P, --Perl-regexp
          Interpret  PATTERN  as a Perl regular expression (PCRE, see
          below).  This is highly experimental and grep -P may warn of
          unimplemented features.

C'est à partir de GNU grep 2.10.

12
sage

Cela peut être fait facilement en utilisant d'abord tr pour remplacer les nouvelles lignes par un autre caractère:

tr '\n' '\a' | grep 'abc.*def' | tr '\a' '\n'

Ici, j’utilise le caractère d’alarme, \a (ASCII 7) à la place d’un retour à la ligne . Cela n’est presque jamais trouvé dans votre texte et grep peut le faire correspondre à un . ou le faire correspondre spécifiquement à \a.

9
g.rocket

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

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

Vous pouvez également 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 risque de surcharger la mémoire utilisée par de 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
6
sundar

Je ne sais pas comment je ferais cela avec grep, mais je ferais quelque chose comme ça avec awk:

awk '/abc/{ln1=NR} /efg/{ln2=NR} END{if(ln1 && ln2 && ln1 < ln2){print "found"}else{print "not found"}}' foo

Vous devez cependant faire attention à comment vous faites cela. Voulez-vous que l'expression régulière corresponde à la sous-chaîne ou au mot entier? ajoutez des balises\w selon le cas. De plus, bien que cela soit strictement conforme à la façon dont vous avez donné l’exemple, cela ne fonctionne pas tout à fait lorsque abc apparaît une deuxième fois après efg. Si vous voulez gérer cela, ajoutez un if comme approprié dans/abc/case, etc.

5
frankc

awk one-liner:

awk '/abc/,/efg/' [file-with-content]
4
Swynndla

Il y a quelques jours, j'ai publié une alternative à grep qui prend en charge cette option directement, soit via une correspondance multiligne, soit en utilisant des conditions. Espérons qu'elle sera utile pour certaines personnes effectuant une recherche ici. Voici à quoi ressemblent les commandes de l'exemple:

Multiline: sift -lm 'abc.*efg' testfile 
Conditions: sift -l 'abc' testfile --followed-by 'efg'

Vous pouvez également spécifier que 'efg' doit suivre 'abc' dans un certain nombre de lignes:
sift -l 'abc' testfile --followed-within 5:'efg'

Vous pouvez trouver plus d'informations sur sift-tool.org .

3
svent

Si vous avez besoin que les deux mots soient proches, par exemple pas plus de 3 lignes, vous pouvez le faire:

find . -exec grep -Hn -C 3 "abc" {} \; | grep -C 3 "efg"

Même exemple mais en filtrant uniquement les fichiers * .txt:

find . -name *.txt -exec grep -Hn -C 3 "abc" {} \; | grep -C 3 "efg"

Et vous pouvez également remplacer la commande grep par la commande egrep si vous souhaitez également rechercher des expressions régulières.

2
Mariano Ruiz

Malheureusement, vous ne pouvez pas. De la grep docs:

grep recherche dans les fichiers nommés (ou dans l’entrée standard si aucun fichier n’est nommé, ou si un seul tiret - moins (-) est donné comme nom de fichier) pour lignes contenant une correspondance avec le motif donné.

2
Kaleb Pederson

Si vous êtes prêt à utiliser des contextes, vous pouvez le faire en tapant

grep -A 500 abc test.txt | grep -B 500 efg

Cela affichera tout entre "abc" et "efg", à condition qu'ils soient à moins de 500 lignes l'un de l'autre.

2
agouge

Bien que l’option sed soit la plus simple et la plus facile, le one-liner de LJ n’est malheureusement pas le plus portable. Ceux qui sont coincés avec une version du C Shell devront échapper à leur frange:

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

Cela ne fonctionne malheureusement pas dans bash et al.

2
bug

vous pouvez utiliser grep si vous n'êtes pas intéressé par la séquence du motif.

grep -l "pattern1" filepattern*.* | xargs grep "pattern2"

exemple

grep -l "vector" *.cpp | xargs grep "map"

grep -l trouvera tous les fichiers qui correspondent au premier motif et xargs va grep pour le deuxième motif. J'espère que cela t'aides.

1
Balu Mohan

Avec chercheur d'argent :

ag 'abc.*(\n|.)*efg'

semblable à la réponse du porteur de l'anneau, mais avec ag ​​à la place. Les avantages de vitesse du chercheur d'argent pourraient éventuellement briller ici.

1
Shwaydogg
#!/bin/bash
shopt -s nullglob
for file in *
do
 r=$(awk '/abc/{f=1}/efg/{g=1;exit}END{print g&&f ?1:0}' file)
 if [ "$r" -eq 1 ];then
   echo "Found pattern in $file"
 else
   echo "not found"
 fi
done
1
ghostdog74

J'ai utilisé cela pour extraire une séquence fasta d'un fichier multi fasta en utilisant l'option -P pour grep:

grep -Pzo ">tig00000034[^>]+" file.fasta > desired_sequence.fasta

-P pour les recherches basées sur Perl -z pour que la ligne se termine par 0 octet plutôt que par une nouvelle ligne car -o pour capturer simplement ce qui correspond, puisque grep renvoie la ligne entière (ce qui dans ce cas, puisque -z est le fichier entier). Le noyau de l'expression rationnelle est le [^>] qui se traduit par "pas plus grand que le symbole"

1
Jon Boyle

Le filepattern *.sh est important pour empêcher l'inspection de répertoires. Bien sûr, certains tests pourraient empêcher cela aussi.

for f in *.sh
do
  a=$( grep -n -m1 abc $f )
  test -n "${a}" && z=$( grep -n efg $f | tail -n 1) || continue 
  (( ((${z/:*/}-${a/:*/})) > 0 )) && echo $f
done

Le

grep -n -m1 abc $f 

recherche au maximum 1 correspondance et retourne (-n) le linumber. Si une correspondance a été trouvée (test -n ...), trouvez la dernière correspondance de efg (trouvez tout et prenez la dernière avec tail -n 1).

z=$( grep -n efg $f | tail -n 1)

sinon continuer.

Puisque le résultat est quelque chose comme 18:foofile.sh String alf="abc";, nous devons couper de ":" jusqu'à la fin de la ligne.

((${z/:*/}-${a/:*/}))

Devrait renvoyer un résultat positif si le dernier match de la deuxième expression est passé après le premier match du premier. 

Ensuite, nous rapportons le nom de fichier echo $f.

0
user unknown

Si vous avez une estimation de la distance entre les 2 chaînes 'abc' et 'efg' que vous recherchez, vous pouvez utiliser:

grep -r. -e 'abc' -A num1 -B num2 | grep 'efg'

De cette façon, le premier grep renverra la ligne avec les lignes 'abc' plus # num1 après et les lignes # num2 après, et le second grep passera en revue toutes celles pour obtenir le 'efg'. Ensuite, vous saurez quels fichiers ils apparaissent ensemble.

0
Benjamin Berend

En guise d'alternative à la réponse de Balu Mohan, il est possible d'appliquer l'ordre des motifs en utilisant uniquement grep, head et tail:

for f in FILEGLOB; do tail $f -n +$(grep -n "pattern1" $f | head -n1 | cut -d : -f 1) 2>/dev/null | grep "pattern2" &>/dev/null && echo $f; done

Celui-ci n'est pas très joli, cependant. Formaté plus lisiblement:

for f in FILEGLOB; do
    tail $f -n +$(grep -n "pattern1" $f | head -n1 | cut -d : -f 1) 2>/dev/null \
    | grep -q "pattern2" \
    && echo $f
done

Ceci imprimera les noms de tous les fichiers où "pattern2" apparaît après "pattern1", ou si les deux apparaissent sur la même ligne :

$ echo "abc
def" > a.txt
$ echo "def
abc" > b.txt
$ echo "abcdef" > c.txt; echo "defabc" > d.txt
$ for f in *.txt; do tail $f -n +$(grep -n "abc" $f | head -n1 | cut -d : -f 1) 2>/dev/null | grep -q "def" && echo $f; done
a.txt
c.txt
d.txt

Explication

  • tail -n +i - affiche toutes les lignes après la ie incluse
  • grep -n - ajoute des lignes correspondantes avec leurs numéros
  • head -n1 - n'imprime que la première ligne
  • cut -d : -f 1 - affiche la première colonne coupée en utilisant : comme délimiteur
  • 2>/dev/null - silence tail sortie d'erreur qui se produit si l'expression $() renvoie vide
  • grep -q - silence grep et retourne immédiatement si une correspondance est trouvée, car nous ne sommes intéressés que par le code de sortie
0
Emil Lundberg

Cela devrait fonctionner aussi?!

Perl -lpne 'print $ARGV if /abc.*?efg/s' file_list

$ARGV contient le nom du fichier actuel lors de la lecture à partir de file_list /s modificateur effectue une recherche sur une nouvelle ligne. 

0
PS12