Comment utiliser bash (grep / sed / etc) pour récupérer une section de fichier journal entre 2 horodatages?

StackOverflow https://stackoverflow.com/questions/827930

Question

J'ai un ensemble de journaux de messagerie: mail.log mail.log.0 mail.log.1.gz mail.log.2.gz

chacun de ces fichiers contient des lignes triées chronologiquement commençant par des horodatages tels que:

3 mai 13:21:12 ...

Comment puis-je récupérer facilement chaque entrée de journal après une date / heure donnée et avant une autre date / heure à l'aide de bash (et des outils de ligne de commande associés) sans comparer chaque ligne? N'oubliez pas que mes dates avant et après peuvent ne pas correspondre exactement aux entrées des fichiers journaux.

Il me semble que je dois déterminer le décalage de la première ligne supérieur à l'horodatage de départ et celui de la dernière ligne inférieur à l'horodatage final, puis couper cette section d'une manière ou d'une autre.

Était-ce utile?

La solution 2

Voici une idée de base sur la façon de le faire:

  1. Examinez le horodatage du fichier pour voir s'il ne s'agit pas d'un événement pertinent
  2. Si cela peut être pertinent, décompressez-le si nécessaire et examinez les premières et dernières lignes du fichier pour voir s'il contient l'heure de début ou de fin.
  3. Si tel est le cas, utilisez une fonction récursive pour déterminer si elle contient l'heure de début dans la première ou la seconde moitié du fichier. En utilisant une fonction récursive, je pense que vous pouvez trouver n’importe quelle date dans un fichier journal de millions de lignes avec environ 20 comparaisons.
  4. renvoie le ou les fichiers journaux dans l’ordre allant du décalage de la première entrée au décalage de la dernière entrée (plus de comparaisons)

Ce que je ne sais pas, c'est: comment lire au mieux la nième ligne d'un fichier (quelle est l'efficacité de l'utilisation de tail n + ** n | head 1 **?)

Avez-vous de l'aide?

Autres conseils

Convertissez vos dates minimum / maximum en "secondes depuis l'époque",

MIN=`date --date="$1" +%s`
MAX=`date --date="$2" +%s`

Convertissez les mêmes n mots de chaque ligne du journal en identiques,

L_DATE=`echo $LINE | awk '{print $1 $2 ... $n}'`
L_DATE=`date --date="$L_DATE" +%s`

Comparez et jetez les lignes jusqu'à atteindre MIN ,

.
if (( $MIN > $L_DATE )) ; then continue ; fi

Comparez et imprimez les lignes jusqu'à atteindre MAX ,

.
if (( $L_DATE <= $MAX )) ; then echo $LINE ; fi

Quittez lorsque vous dépassez MAX .

if (( $L_DATE > $MAX )) ; then exit 0 ; fi

Le script entier minmaxlog.sh ressemble à ceci,

#!/usr/bin/env bash

MIN=`date --date="$1" +%s`
MAX=`date --date="$2" +%s`

while true ; do
    read LINE
    if [ "$LINE" = "" ] ; then break ; fi

    L_DATE=`echo $LINE | awk '{print $1 " " $2 " " $3 " " $4}'`
    L_DATE=`date --date="$L_DATE" +%s`

    if (( $MIN > $L_DATE  )) ; then continue ; fi
    if (( $L_DATE <= $MAX )) ; then echo $LINE ; fi
    if (( $L_DATE >  $MAX )) ; then break ; fi
done

Je l'ai exécuté sur ce fichier minmaxlog.input ,

May 5 12:23:45 2009 first line
May 6 12:23:45 2009 second line
May 7 12:23:45 2009 third line
May 9 12:23:45 2009 fourth line
June 1 12:23:45 2009 fifth line
June 3 12:23:45 2009 sixth line

comme ça,

./minmaxlog.sh "May 6" "May 8" < minmaxlog.input

Vous devez examiner chaque ligne de la plage que vous souhaitez (pour savoir si elle se situe dans la plage de votre choix), je suppose donc que vous ne voulez pas dire que toutes les lignes du fichier. Au minimum, vous devrez examiner chaque ligne du fichier, y compris la première en dehors de votre plage (je suppose que les lignes sont classées par date / heure).

Ceci est un modèle assez simple:

state = preprint
for every line in file:
    if line.date >= startdate:
        state = print
    if line.date > enddate:
        exit for loop
    if state == print:
        print line

Vous pouvez écrire cela dans awk, Perl, Python, voire COBOL si vous le devez, mais la logique est toujours la même.

Localiser d’abord les numéros de ligne (avec par exemple grep), puis simplement imprimer aveuglément cette plage de lignes ne sera d'aucune aide, car grep doit également examiner toutes les lignes ( toutes , pas seulement jusqu'au premier en dehors de la plage et le plus probablement deux fois , un pour la première ligne et un pour la dernière).

Si vous faites cela souvent, vous pouvez envisager de déplacer l'effort de "chaque fois que vous le faites" à "une fois, lorsque le fichier est stabilisé". Un exemple serait de charger les lignes du fichier journal dans une base de données, indexée par date / heure.

Cela prend un certain temps pour être configuré, mais vos requêtes deviendront beaucoup plus rapides. Je ne préconise pas nécessairement une base de données. Vous pouvez probablement obtenir le même effet en scindant les fichiers journaux en journaux horaires, comme suit:

2009/
  01/
    01/
      0000.log
      0100.log
      : :
      2300.log
    02/
    : :

Ensuite, pour un temps donné, vous savez exactement par où commencer et vous ne cherchez plus. La plage 2009/01 / 01-15: 22 à 2009/01 / 05-09: 07 donnerait le résultat:

  • certains (le dernier bit) du fichier 2009/01/01 / 1500.txt .
  • tous les fichiers 2009/01/01/1 [6-9] *. txt .
  • tous les fichiers 2009/01/01/2 * .txt .
  • tous les fichiers 2009/01/0 [2-4] / *. txt .
  • tous les fichiers 2009/01/05/0 [0-8] *. txt .
  • certains (le premier bit) du fichier 2009/01/05 / 0900.txt .

Bien sûr, j'écrirais un script pour renvoyer ces lignes plutôt que d'essayer de le faire manuellement à chaque fois.

Peut-être que vous pouvez essayer ceci:

sed -n "/BEGIN_DATE/,/END_DATE/p" logfile

Cela est peut-être possible dans un environnement Bash, mais vous devez vraiment tirer parti des outils offrant une prise en charge plus intégrée pour l'utilisation de chaînes et de dates. Par exemple, Ruby semble avoir la capacité intégrée d’analyser votre format de date. Il peut ensuite le convertir en un horodatage Unix facilement comparable (un entier positif représentant les secondes depuis l’époque).

irb> require 'time'
# => true

irb> Time.parse("May 3 13:21:12").to_i
# => 1241371272  

Vous pouvez alors écrire facilement un script Ruby:

  • Fournissez une date de début et de fin. Convertissez-les en ce numéro d'horodatage Unix.
  • Parcourez les fichiers journaux ligne par ligne en convertissant la date en son horodatage Unix et vérifiez si elle se trouve dans la plage des dates de début et de fin.

Remarque: La conversion en un entier Unix Timestamp est une bonne première, car il est très facile et efficace de comparer des entiers.

Vous avez mentionné "sans comparer chaque ligne". Il sera difficile de "deviner" à l'endroit où, dans le fichier journal, les entrées commencent à être trop anciennes ou trop nouvelles sans vérifier toutes les valeurs entre les deux Cependant, s'il existe effectivement une tendance à la hausse monotone, vous savez immédiatement quand arrêter l'analyse des lignes, car dès que la prochaine entrée est trop nouvelle (ou ancienne, en fonction de la disposition des données), vous savez que vous pouvez arrêter la recherche. Reste le problème de trouver la première ligne dans la plage souhaitée.

Je viens de remarquer votre modification. Voici ce que je dirais:

Si vous craignez vraiment de trouver efficacement cette entrée de début et de fin, vous pouvez alors effectuer une recherche binaire pour chacune d’elles. Ou, si cela vous semble excessif ou trop difficile avec des outils bash, vous pourriez avoir une heuristique consistant à ne lire que 5% des lignes (1 sur 20), pour obtenir rapidement une réponse presque exacte et affiner ensuite celle-ci si vous le souhaitez. Ce ne sont là que quelques suggestions pour améliorer les performances.

Je sais que ce fil est ancien, mais je suis tombé dessus après avoir récemment trouvé une solution en une ligne pour mes besoins:

awk -v ts_start="2018-11-01" -v ts_end="2018-11-15" -F, '$1>=ts_start && $1<ts_end' myfile

Dans ce cas, mon fichier contient des enregistrements avec des valeurs séparées par des virgules et l’horodatage dans le premier champ. Vous pouvez utiliser n'importe quel format d'horodatage valide pour les horodatages de début et de fin, et remplacer ces variables shell si vous le souhaitez.

Si vous souhaitez écrire dans un nouveau fichier, utilisez simplement la redirection de sortie normale ( > newfile ) ajoutée à la fin de ci-dessus.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top