Comment puis-je grep récursivement à travers les fichiers .gz?

135

J'utilise un script pour télécharger régulièrement mes messages Gmail qui compresse le fichier .eml brut en fichiers .gz. Le script crée un dossier pour chaque jour, puis compresse chaque message dans son propre fichier.

Je voudrais un moyen de rechercher dans cette archive une "chaîne".

Grep seul ne semble pas le faire. J'ai aussi essayé SearchMonkey.

Kendor
la source
16
utiliser zgrep:zgrep - search possibly compressed files for a regular expression
Arkadiusz Drabczyk

Réponses:

142

Si vous voulez grep récursivement dans tous les fichiers .eml.gz du répertoire en cours, vous pouvez utiliser:

find . -name \*.eml.gz -print0 | xargs -0 zgrep "STRING"

Vous devez échapper au premier *afin que le shell ne l'interprète pas. -print0indique à find d'imprimer un caractère nul après chaque fichier trouvé; xargs -0lit à partir de l'entrée standard et exécute la commande après pour chaque fichier; zgrepfonctionne comme grep, mais décompresse d'abord le fichier.

JK Stafford
la source
3
'-print0' et '-0' ne sont pas obligatoires. xargs utilise '\ n' par défaut.
Jaime M.
1
Ils sont nécessaires s'il peut y avoir des espaces dans les chemins; il n'y a pas d'autre raison que la complexité de ne pas les utiliser.
Daniel Griscom
2
zgrepsemble en fait plus rapide que de grepfonctionner sur des fichiers non compressés. Cela doit être dû au fait que les fichiers compressés peuvent être lus sur le disque dur et décompressés plus rapidement que la lecture d’un fichier non compressé à partir du disque dur.
Geremia
@ JaimeM. xargsutilise des blancs (espaces) par défaut. Bien sûr, les fichiers ne contiennent presque jamais de nouvelles lignes, mais les espaces ne sont pas inconnus (même si la plupart des types UNIXy les désapprouvent). Cela dit, vous pouvez simplifier encore plus les choses sans vous soucier des espaces: vous obtenez find . -name '*.eml.gz' -exec zgrep "STRING" {} +le même nombre d'arguments lors du lancement xargs, la sécurité de -print0/ -0et le tout sans les frais généraux liés au lancement d'un processus supplémentaire et à la création d'une tuyauterie, et de manière assez concise. -execavec +est spécifié Posix, il devrait donc être sur la plupart des semi-récents systèmes de type UNIX à ma connaissance.
ShadowRanger
@ Jared Existe-t-il un moyen de faire une recherche générique en ne connaissant que le début du modèle de fichier? Par exemple, j'ai des fichiers .gz qui ont des horodatages à la fin. ABCLog04_18_18_2_21.gz Existe-t-il un moyen de rechercher récursivement des fichiers commençant par ABC *. J'ai essayé de remplacer \*.eml.gzdans votre exemple ci-dessus avec ABCLog*et obtenir une erreur sur le format de fichier .:find: paths must precede expression: ABCLog-2018-03-12-10-16-1.log.gz Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]
DevelopingDeveloper
68

Il y a beaucoup de confusion ici car il n'y en a pas qu'un zgrep. J'ai deux versions sur mon système, zgrepde gzipet zgrepde zutils. Le premier est juste un script wrapper qui appelle gzip -cdfq. Il ne supporte pas le -r, --recursivecommutateur. 1
Ce dernier est un c++programme et il prend en charge l' -r, --recursiveoption.
Running zgrep --version | head -n 1indiquera lequel (le cas échéant) est la valeur par défaut:

zgrep (gzip) 1.6

est le script wrapper,

zgrep (zutils) 1.3

est l' cppexécutable.
Si vous avez ce dernier, vous pouvez exécuter:

zgrep 'pattern' -r --format=gz /path/to/dir

Quoi qu'il en soit, comme suggéré, find+ zgrepfonctionnera également bien avec l'une ou l'autre version de zgrep:

find /path/to/dir -name '*.gz' -exec zgrep -- 'pattern' {} +

Si zgrepest absent de votre système (hautement improbable), vous pouvez essayer avec:

find /path/to/dir -name '*.gz' -exec sh -c 'gzip -cd "$0" | grep -- "pattern"' {} \;

mais il y a un inconvénient majeur: vous ne saurez pas où se trouvent les correspondances car aucun nom de fichier n'est ajouté aux lignes correspondantes.


1: parce que ce serait problématique

don_crissti
la source
1
si zgrepde zutils n'est pas disponible, vous pouvez l'installer dans Ubuntu avec sudo apt-get install zutils.
therealmarv
1
Suite de @therealmarv ... et ensuite Ubuntu utilisera zutils zgrep au lieu de gzip. Alors -r fonctionne!
Elijah Lynn
Existe-t-il un moyen d’imprimer le numéro de ligne du fichier sur lequel le motif est apparié?
DogEatDog
@DogEatDog - juste comme grep -n, zgrep -naffichera le numéro de ligne. C'est dans le manuel ...
don_crissti
7

agest une variante de grep, avec quelques fonctionnalités supplémentaires intéressantes.

  • a l'option -z pour les fichiers compressés,
  • a beaucoup de fonctionnalités ack.
  • c'est rapide

Alors:

ag -r -z your-pattern-goes-here   folder

Si non installé,

apt-get install silversearcher-ag   (debian and friends)
yum install the_silver_searcher     (fedora)
brew install the_silver_searcher    (mac)
JJoao
la source
1
Je reçois ag: truncated file: Successun résultat. Tout autre drapeau dois-je ajouter?
Janvier
4

La récursion seule est facile:

   -r, --recursive
          Read all files  under  each  directory,  recursively,  following
          symbolic  links  only  if they are on the command line.  This is
          equivalent to the -d recurse option.

   -R, --dereference-recursive
          Read all files under each directory,  recursively.   Follow  all
          symbolic links, unlike -r.

Cependant, pour les fichiers compressés, vous avez besoin de quelque chose comme:

shopt globstar 
for file in /path/to/directory/**/*gz; do zcat ""$file" | grep pattern; done

path/to/directory devrait être le répertoire parent contenant les sous-répertoires de chaque jour.


zgrepest la réponse évidente, mais, malheureusement, il ne supporte pas le -rdrapeau. De man zgrep:

Ces options de grep entraîneront l'arrêt de zgrep avec un code d'erreur: (- [d rR zZ ] | --di * | --exc * | --inc * | --rec * | --nu *).

terdon
la source
3

Si votre système a zgrep, vous pouvez simplement

zgrep -irs your-pattern-goes-here the-folder-to-search-goes-here/

Si votre système ne dispose pas de zgrep, vous pouvez utiliser la commande find pour exécuter zcat et grep sur chaque fichier de la manière suivante:

find the-folder-to-search-goes-here/ -name '*.gz' \ -exec sh -c 'echo "Searching {}" ; zcat "{}" | grep your-pattern-goes-here ' \;

Nate de Kalamazoo
la source
Pardonnez-moi la greeness à ce sujet ... les fichiers à rechercher sont une couche de profondeur. ~ / gmvault-db / db / 2015-02 contient un dossier pour chaque mois archivé, puis les fichiers .gz de ce mois sont stockés. Si je cherche .mil dans tout cet arbre, est-ce ce que je ferais? recherchez ~ / gmvault-db / db / -name '* .gz' \ -exec sh -c 'echo "Searching {}"; zcat "{}" | grep .mil '\;
Kendor
1
C'est bien - le "r" dans -sirs fera que zgrep effectuera une recherche récursive. La commande find fonctionne de manière récursive par défaut. Ainsi, tout fichier se terminant par .gz sera zcatté et transmis à grep. (et le {} sera étendu au chemin relatif du fichier qui va être recherché). Ainsi, lorsque vous obtiendrez un succès, il sera précédé de Searching ~/gmvault-db/db/2015-02/03/whatever.gz
Nate de Kalamazoo
Voici ce que je récupère: find: "les chemins doivent précéder l'expression: -exec" Voici la commande que j'ai utilisée: find ~ / gmvault-db / db / -name '* .gz' \ -exec sh -c 'echo "Searching { } "; zcat "{}" | grep .mil '\;
Kendor
supprime la barre oblique inverse entre le '* .gz' et le -exec.
Nate de Kalamazoo
4
zgrepne prendra pas le -rdrapeau pour une raison quelconque. C'est mentionné dans man zgrep(voir aussi ma réponse).
terdon
0

xzgrep -l "chaîne" ./*/*.eml.gz

xzgrep est un dérivé des utils zgrep (less / bin / xzgrep)

Depuis la page de manuel:

xzgrep appelle grep (1) sur les fichiers décompressés ou compressés avec xz (1), lzma (1), gzip (1), bzip2 (1) ou lzop (1). Toutes les options spécifiées sont passées directement à grep (1).

-l affiche le nom du fichier correspondant

-R pour la récursion ne fonctionnera pas car il est spécifiquement interdit dans le script, mais une simple manipulation de shell devrait nous y amener

./*/*.eml.gz

à partir d'un chemin relatif où ./today/sample.eml.gz, une correspondance sur toutes les instances correspondant à un niveau inférieur à notre position relative dans le shell, qui se termine par ".eml.gz"

John
la source