Je cherche un moyen de répertorier tous les fichiers d'un répertoire contenant l'ensemble complet des mots clés que je recherche, n'importe où dans le fichier.
Ainsi, les mots clés n'ont pas besoin d'apparaître sur la même ligne.
Une façon de procéder serait:
grep -l one $(grep -l two $(grep -l three *))
Trois mots clés ne sont qu'un exemple, il pourrait tout aussi bien être deux ou quatre, et ainsi de suite.
Une deuxième façon de penser est:
grep -l one * | xargs grep -l two | xargs grep -l three
Une troisième méthode, apparue dans une autre question , serait:
find . -type f \
-exec grep -q one {} \; -a \
-exec grep -q two {} \; -a \
-exec grep -q three {} \; -a -print
Mais ce n'est certainement pas la direction que je prends ici. Je veux quelque chose qui nécessite moins de frappe, et peut - être juste un appel à grep
, awk
, perl
ou similaire.
Par exemple, j'aime comment awk
vous permet de faire correspondre des lignes qui contiennent tous les mots clés , comme:
awk '/one/ && /two/ && /three/' *
Ou, imprimez uniquement les noms de fichiers:
awk '/one/ && /two/ && /three/ { print FILENAME ; nextfile }' *
Mais je veux trouver des fichiers où les mots-clés peuvent être n'importe où dans le fichier, pas nécessairement sur la même ligne.
Les solutions préférées seraient gzip friendly, par exemple grep
a la zgrep
variante qui fonctionne sur les fichiers compressés. Pourquoi je mentionne cela, c'est que certaines solutions peuvent ne pas fonctionner correctement compte tenu de cette contrainte. Par exemple, dans l' awk
exemple d'impression de fichiers correspondants, vous ne pouvez pas simplement faire:
zcat * | awk '/pattern/ {print FILENAME; nextfile}'
Vous devez modifier considérablement la commande, en quelque chose comme:
for f in *; do zcat $f | awk -v F=$f '/pattern/ { print F; nextfile }'; done
Donc, en raison de la contrainte, vous devez appeler awk
plusieurs fois, même si vous ne pouvez le faire qu'une seule fois avec des fichiers non compressés. Et certainement, il serait plus agréable de le faire zawk '/pattern/ {print FILENAME; nextfile}' *
et d'obtenir le même effet, donc je préférerais des solutions qui permettent cela.
gzip
conviviaux, justezcat
les fichiers en premier.grep
solutions sont facilement adaptables simplement en préfixant lesgrep
appels avec unz
, il n'est pas nécessaire que je gère également les noms de fichiers.grep
. AFAIK, uniquementgrep
etcat
ont des "z-variantes" standard. Je ne pense pas que vous obtiendrez quelque chose de plus simple que d'utiliser unefor f in *; do zcat -f $f ...
solution. Tout le reste devrait être un programme complet qui vérifie les formats de fichiers avant d'ouvrir ou utilise une bibliothèque pour faire de même.Réponses:
Si vous souhaitez gérer automatiquement les fichiers gzippés, exécutez-le en boucle avec
zcat
(lent et inefficace car vous bifurquerezawk
plusieurs fois dans une boucle, une fois pour chaque nom de fichier) ou réécrivez le même algorithmeperl
et utilisez leIO::Uncompress::AnyUncompress
module de bibliothèque qui peut décompressez plusieurs types de fichiers compressés (gzip, zip, bzip2, lzop). ou en python, qui a également des modules pour gérer les fichiers compressés.Voici une
perl
version qui utiliseIO::Uncompress::AnyUncompress
pour autoriser un nombre illimité de modèles et un nombre illimité de noms de fichiers (contenant du texte brut ou du texte compressé).Tous les arguments avant
--
sont traités comme des modèles de recherche. Tous les arguments après--
sont traités comme des noms de fichiers. Gestion des options primitive mais efficace pour ce travail. Une meilleure gestion des options (par exemple pour prendre en charge une-i
option pour les recherches non sensibles à la casse) pourrait être obtenue avec les modulesGetopt::Std
ouGetopt::Long
.Exécutez-le comme ceci:
(Je ne répertorierai pas les fichiers
{1..6}.txt.gz
et{1..6}.txt
ici ... ils contiennent juste une partie ou la totalité des mots "un" "deux" "trois" "quatre" "cinq" et "six" pour les tests. Les fichiers répertoriés dans la sortie ci-dessus Contenez les trois modèles de recherche. Testez-le vous-même avec vos propres données)Un hachage
%patterns
contient l'ensemble complet de modèles que les fichiers doivent contenir au moins un de chaque membre$_pstring
est une chaîne contenant les clés triées de ce hachage. La chaîne$pattern
contient une expression régulière précompilée également construite à partir du%patterns
hachage.$pattern
est comparé à chaque ligne de chaque fichier d'entrée (en utilisant le/o
modificateur pour compiler$pattern
une seule fois car nous savons qu'il ne changera jamais pendant l'exécution), etmap()
est utilisé pour créer un hachage (% s) contenant les correspondances pour chaque fichier.Chaque fois que tous les modèles ont été vus dans le fichier actuel (en comparant si
$m_string
(les clés triées dans%s
) sont égales à$p_string
), imprimez le nom de fichier et passez au fichier suivant.Ce n'est pas une solution particulièrement rapide, mais ce n'est pas excessivement lent. La première version a pris 4 min 58 s pour rechercher trois mots dans 74 Mo de fichiers journaux compressés (totalisant 937 Mo non compressés). Cette version actuelle prend 1m13s. Il y a probablement d'autres optimisations qui pourraient être faites.
Une optimisation évidente consiste à l'utiliser en conjonction avec
xargs
's-P
aka--max-procs
pour exécuter plusieurs recherches sur des sous-ensembles de fichiers en parallèle. Pour ce faire, vous devez compter le nombre de fichiers et diviser par le nombre de cœurs / cpus / threads de votre système (et arrondir en ajoutant 1). Par exemple, 269 fichiers ont été recherchés dans mon jeu d'échantillons et mon système a 6 cœurs (un AMD 1090T), donc:Avec cette optimisation, il n'a fallu que 23 secondes pour trouver les 18 fichiers correspondants. Bien sûr, la même chose pourrait être faite avec n'importe quelle autre solution. REMARQUE: l'ordre des noms de fichiers répertoriés dans la sortie sera différent, il peut donc être nécessaire de les trier ultérieurement si cela est important.
Comme indiqué par @arekolek, plusieurs
zgrep
s avecfind -exec
ouxargs
peuvent le faire beaucoup plus rapidement, mais ce script a l'avantage de prendre en charge un certain nombre de modèles à rechercher et est capable de gérer plusieurs types de compression différents.Si le script se limite à examiner uniquement les 100 premières lignes de chaque fichier, il les traverse toutes (dans mon échantillon de 74 Mo de 269 fichiers) en 0,6 seconde. Si cela est utile dans certains cas, il pourrait être transformé en une option de ligne de commande (par exemple
-l 100
) mais il a le risque de ne pas trouver tous les fichiers correspondants.BTW, selon la page de manuel de
IO::Uncompress::AnyUncompress
, les formats de compression pris en charge sont:Une dernière (j'espère) optimisation. En utilisant le
PerlIO::gzip
module (empaqueté dans debian aslibperlio-gzip-perl
) au lieu deIO::Uncompress::AnyUncompress
j'ai réduit le temps à environ 3,1 secondes pour traiter mes 74 Mo de fichiers journaux. Il y a également eu quelques petites améliorations en utilisant un hachage simple plutôt queSet::Scalar
(ce qui a également sauvé quelques secondes avec leIO::Uncompress::AnyUncompress
version).PerlIO::gzip
a été recommandé comme le gunzip perl le plus rapide dans /programming//a/1539271/137158 (trouvé avec une recherche google pourperl fast gzip decompress
)Utiliser
xargs -P
avec cela ne l'a pas amélioré du tout. En fait, il semblait même le ralentir de 0,1 à 0,7 seconde. (J'ai essayé quatre runs et mon système fait d'autres choses en arrière-plan qui modifieront le timing)Le prix est que cette version du script ne peut gérer que les fichiers compressés et compressés. Vitesse vs flexibilité: 3,1 secondes pour cette version vs 23 secondes pour la
IO::Uncompress::AnyUncompress
version avecxargs -P
wrapper (ou 1m13 sansxargs -P
).la source
for f in *; do zcat $f | awk -v F=$f '/one/ {a++}; /two/ {b++}; /three/ {c++}; a&&b&&c { print F; nextfile }'; done
fonctionne bien, mais en effet, prend 3 fois plus de temps que magrep
solution, et est en fait plus compliqué.apt-get install libset-scalar-perl
utiliser le script. Mais cela ne semble pas se terminer dans un délai raisonnable.Définissez le séparateur d'enregistrements sur
.
afin queawk
le fichier entier soit traité comme une seule ligne:De même avec
perl
:la source
for f in *; do zcat $f | awk -v RS='.' -v F=$f '/one/ && /two/ && /three/ { print F }'; done
ne produit rien.zcat -f "$f"
si certains fichiers ne sont pas compressés.awk -v RS='.' '/bfs/&&/none/&&/rgg/{print FILENAME}' greptest/*.txt
ne renvoie toujours aucun résultat, tandis quegrep -l rgg $(grep -l none $(grep -l bfs greptest/*.txt))
renvoie les résultats attendus.Pour les fichiers compressés, vous pouvez parcourir chaque fichier en boucle et décompresser en premier. Ensuite, avec une version légèrement modifiée des autres réponses, vous pouvez faire:
Le script Perl se terminera avec le
0
statut (succès) si les trois chaînes ont été trouvées. C'est l'}{
abréviation de PerlEND{}
. Tout ce qui suit sera exécuté une fois toutes les entrées traitées. Le script se terminera donc avec un état de sortie différent de 0 si toutes les chaînes n'ont pas été trouvées. Par conséquent, le&& printf '%s\n' "$f"
affichera le nom du fichier uniquement si les trois ont été trouvés.Ou, pour éviter de charger le fichier en mémoire:
Enfin, si vous voulez vraiment tout faire dans un script, vous pouvez faire:
Enregistrez le script ci-dessus comme
foo.pl
quelque part dans votre$PATH
, rendez-le exécutable et exécutez-le comme ceci:la source
De toutes les solutions proposées jusqu'à présent, ma solution originale utilisant grep est la plus rapide, terminant en 25 secondes. Son inconvénient est qu'il est fastidieux d'ajouter et de supprimer des mots clés. J'ai donc trouvé un script (doublé
multi
) qui simule le comportement, mais permet de changer la syntaxe:Alors maintenant, l'écriture
multi grep one two three -- *
est équivalente à ma proposition d'origine et s'exécute en même temps. Je peux également l'utiliser facilement sur des fichiers compressés en utilisantzgrep
comme premier argument à la place.Autres solutions
J'ai également expérimenté un script Python en utilisant deux stratégies: rechercher tous les mots clés ligne par ligne et rechercher dans le fichier entier mot clé par mot clé. La deuxième stratégie a été plus rapide dans mon cas. Mais c'était plus lent que la simple utilisation
grep
, terminant en 33 secondes. La correspondance des mots clés ligne par ligne s'est terminée en 60 secondes.Le script donné par terdon s'est terminé en 54 secondes. En fait, cela a pris 39 secondes de temps de mur, car mon processeur est dual core. Ce qui est intéressant, car mon script Python a pris 49 secondes de temps de mur (et
grep
était de 29 secondes).Le script par cas n'a pas pu se terminer dans un délai raisonnable, même sur un plus petit nombre de fichiers traités en
grep
moins de 4 secondes, j'ai donc dû le tuer.Mais sa
awk
proposition originale , même si elle est plus lente qu'actuellementgrep
, a un avantage potentiel. Dans certains cas, du moins d'après mon expérience, il est possible de s'attendre à ce que tous les mots clés apparaissent tous quelque part dans la tête du fichier s'ils le sont. Cela donne à cette solution une amélioration spectaculaire des performances:Termine en un quart de seconde, contre 25 secondes.
Bien sûr, il se peut que nous n'ayons pas l'avantage de rechercher des mots clés connus pour se produire vers le début des fichiers. Dans ce cas, la solution sans
NR>100 {exit}
prend 63 secondes (50 secondes de temps de paroi).Fichiers non compressés
Il n'y a pas de différence significative dans le temps d'exécution entre ma
grep
solution et laawk
proposition de CAS , les deux prennent une fraction de seconde à exécuter.Notez que l'initialisation des variables
FNR == 1 { f1=f2=f3=0; }
est obligatoire dans ce cas pour réinitialiser les compteurs pour chaque fichier traité suivant. En tant que telle, cette solution nécessite de modifier la commande à trois endroits si vous souhaitez modifier un mot clé ou en ajouter de nouveaux. D'un autre côté, avecgrep
vous pouvez simplement ajouter| xargs grep -l four
ou modifier le mot-clé que vous souhaitez.Un inconvénient de la
grep
solution qui utilise la substitution de commandes est qu'elle se bloque si, n'importe où dans la chaîne, avant la dernière étape, il n'y a pas de fichiers correspondants. Cela n'affecte pas laxargs
variante car le canal sera abandonné une fois qu'ilgrep
retournera un état non nul. J'ai mis à jour mon script pour l'utiliserxargs
, je n'ai donc pas à gérer cela moi-même, ce qui rend le script plus simple.la source
not all(p in text for p in patterns)
not
) et il s'est terminé en 32 secondes, donc pas beaucoup d'amélioration, mais c'est certainement plus lisible.PerlIO::gzip
plutôt queIO::Uncompress::AnyUncompress
. prend désormais seulement 3,1 secondes au lieu de 1m13 pour traiter mes 74 Mo de fichiers journaux.eval $(lesspipe)
(par exemple dans votre.profile
, etc.), vous pouvez utiliser à laless
place dezcat -f
et votrefor
wrapper de boucleawk
pourra traiter tout type de fichier quiless
peut (gzip, bzip2, xz, et plus) .... less peut détecter si stdout est un pipe et ne fera que sortir un flux vers stdout s'il l'est.Une autre option - alimentez les mots un par un
xargs
pour qu'il s'exécutegrep
sur le fichier.xargs
peut lui-même être amené à sortir dès qu'une invocation degrep
retours échoue en y retournant255
(consultez laxargs
documentation). Bien sûr, le frai des obus et des fourches impliqués dans cette solution va probablement le ralentir considérablementet pour boucler
la source
_
etfile
? Est-ce que cette recherche dans plusieurs fichiers est passée en argument et renvoie des fichiers contenant tous les mots clés?_
, il est passé en tant que$0
à la coquille engendrée - cela apparaîtrait comme le nom de la commande dans la sortie deps
- je m'en remettrais au maître ici