Rechercher la dernière occurrence de chaîne dans plusieurs fichiers

9

J'ai besoin de rechercher plusieurs fichiers journaux (tous les fichiers générés au cours des dernières 24 heures, tous conservés dans le même répertoire) pour trouver la dernière occurrence d'une chaîne. Voici la commande que j'ai écrite:

find . -mtime 1 | grep fileprefix | xargs grep 'search string' | tail -1

Mais cela ne renvoie que la dernière ligne pour un fichier. Des suggestions sur la façon de modifier cela pour obtenir toutes les lignes?

Lokesh
la source
avez-vous essayé d'inverser la queue et de dernier grep? trouver . -mtime 1 | grep fileprefix | queue xargs -1 | grep 'search string'
Mathieu

Réponses:

4

En supposant que les installations GNU:

find . -mtime -1 -exec bash -c \
'for f; do tac "$f" | grep -m1 fileprefix; done' _ {} +
iruvar
la source
Pouvez-vous expliquer le but de 'bash -c \' car j'utilise déjà le shell bash. Également le but de '_ {} +' à la fin.
Lokesh
@Lokesh, vous pouvez findexécuter des commandes sur des fichiers en utilisant -exec. Avec bash -c, nous générons un bashshell qui parcourt les fichiers trouvés par findet s'exécute tac .. | grep -m1 fileprefixsur chacun
iruvar
J'essayais d'étendre le filtrage des chaînes dans la boucle for en incluant la commande cut ie pour f; faire tac "$ f" | grep -m1 préfixe de fichier | cut -d '' -f4,7-8 mais au moment où je mets la commande cut, cela me donne une erreur de fin de fichier inattendue. Pouvez-vous s'il vous plaît suggérer ce que je fais mal.
Lokesh
@lokesh, utilisez -d" "avec coupe. Citations doubles au lieu de simples
iruvar
1
La findcommande peut filtrer le préfixe du fichier; cela grepne devrait pas être nécessaire pour cela. Il est également surprenant que la chaîne de recherche ne figure pas dans cette réponse.
Jonathan Leffler
8

Si tout est dans un seul répertoire, vous pouvez faire:

for file in *fileprefix*; do
    grep 'search string' "$file" | tail -1
done

S'il s'agit de fichiers volumineux, il peut être utile d'accélérer les choses en utilisant tacpour imprimer le fichier dans l'ordre inverse (dernière ligne en premier), puis grep -m1pour faire correspondre la première occurrence. De cette façon, vous évitez d'avoir à lire l'intégralité du fichier:

for file in *fileprefix*; do
    tac file | grep -m1 'search string'
done

Ces deux supposent qu'il n'y a aucun répertoire correspondant fileprefix. S'il y en a, vous obtiendrez une erreur que vous pouvez simplement ignorer. Si c'est un problème, recherchez uniquement les fichiers:

 for file in *fileprefix*; do
    [ -f "$file" ] && tac file | grep -m1 'search string'
 done

Si vous avez également besoin du nom de fichier imprimé, ajoutez -Hà chaque grepappel. Ou, si votre grepne le prend pas en charge, dites-lui de rechercher également /dev/null. Cela ne changera pas la sortie mais grepétant donné que plusieurs fichiers sont fournis, il affichera toujours le nom du fichier pour chaque hit:

for file in *fileprefix*; do
    grep 'search string' "$file" /dev/null | tail -1
done
terdon
la source
"De cette façon, vous évitez d'avoir à lire tout le fichier" - euh? Non, vous évitez de lire l'intégralité du fichier dans grep mais vous placez l'intégralité du fichier via tac à la place. Il n'est pas clair pour moi que ce serait plus rapide, bien que cela dépende de la correspondance entre le début ou la fin du fichier.
Gilles 'SO- arrête d'être méchant'
@Gilles non, vous ne passez pas le fichier entier tacnon plus. Il sortira dès que la première correspondance sera trouvée. Je viens de tester avec un fichier texte 832M et un motif trouvé sur la dernière ligne. grep -m 1 pattern fileoutil ~ 7 secondes et a tac file | grep -m1 patternpris 0.009.
terdon
4
find . ! -name . -prune -mtime 1 -name 'fileprefix*' \
     -exec sed -se'/searchstring/h;$!d;x' {} +

... fonctionnera si vous avez GNU sedqui prend en charge l' -soption de fichiers séparés et un POSIX find.

Vous devriez probablement ajouter les qualificatifs ! -type dor -type f, cependant, car essayer de lire un répertoire ne sera pas très utile, et restreindre davantage la plage aux fichiers normaux pourrait éviter une lecture suspendue à un fichier de canal ou de périphérique série.

La logique est incroyablement simple - sedécrase son hancien espace avec une copie de toute ligne d'entrée qui correspond searchstring, puis dsupprime de la sortie toutes les lignes d'entrée, mais la dernière pour chaque fichier d'entrée. Quand il arrive à la dernière ligne, il xmodifie ses espaces d'attente et de motif, et donc s'il a searchstringété trouvé pendant qu'il lisait le fichier, la dernière occurrence de ce type sera imprimée automatiquement en sortie, sinon il écrit une ligne vierge. (ajouter /./!dà la fin du sedscript si cela n'est pas souhaitable) .

Cela fera une seule sedinvocation par quelques 65k fichiers d'entrée - ou quelle que soit votre ARG_MAXlimite. Cela devrait être une solution très performante, et est tout simplement implémentée.

Si vous voulez également les noms de fichiers, étant donné un GNU récent, sedvous pouvez les écrire sur des lignes séparées avec la Fcommande, ou bien vous pouvez les faire imprimer finddans une liste distincte par lot en ajoutant le -printprimaire après +.

mikeserv
la source
1

Que diriez-vous:

find . -mtime -1 -name "fileprefix*" -exec sh -c \
'echo "$(grep 'search string' $1 | tail -n 1),$1"' _ {} \;

Ce qui précède vous donne une belle sortie avec la dernière occurrence d'une chaîne de recherche dans chaque fichier suivi du nom du fichier respectif après la virgule (modifiez la partie ", $ 1" sous echo pour changer la mise en forme ou supprimez-la si inutile). Un exemple de sortie qui recherche la chaîne de recherche «10» dans les fichiers avec un préfixe de nom de «fichier» est le suivant:

[dmitry@localhost sourceDir]$ find . -mtime -1 -name "file*" -exec  sh -c 'echo "$(grep '10' $1 | tail -n 1),$1"' _ {} \;
Another data 02 10,./file02.log
Some data 01 10,./file01.log
Yet another data 03 10,./file03.log 
Dmitry Aleks
la source
1
find . -mtime 1 -name 'fileprefix*' -exec grep -Hn 'search string' {} + |
    sort -t: -k1,2 -n | 
    awk -F: '{key=$1 ; $1="" ; $2="" ; gsub(/^  /,"",$0); a[key]=$0} 
             END {for (key in a) { print key ":" a[key] }}'

Celui - ci utilise GNU grepde -Het -noptions pour toujours imprimer à la fois le nom et le numéro de ligne de tous les matches, il trie par nom de fichier et linenumber, et les tuyaux dans awk, qui stocke le dernier match pour chaque nom de fichier dans un tableau, et éventuellement des impressions il.

Une méthode assez brutale, mais ça marche.

cas
la source