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.
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
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]
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.
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
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.
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
.
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
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.
awk one-liner:
awk '/abc/,/efg/' [file-with-content]
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 .
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.
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é.
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.
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.
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.
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.
#!/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
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"
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
.
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.
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
tail -n +i
- affiche toutes les lignes après la i
e inclusegrep -n
- ajoute des lignes correspondantes avec leurs numéroshead -n1
- n'imprime que la première lignecut -d : -f 1
- affiche la première colonne coupée en utilisant :
comme délimiteur2>/dev/null
- silence tail
sortie d'erreur qui se produit si l'expression $()
renvoie videgrep -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 sortieCela 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.