Comment extraire des journaux entre deux horodatages

25

Je veux extraire tous les journaux entre deux horodatages. Certaines lignes peuvent ne pas avoir d'horodatage, mais je veux aussi ces lignes. En bref, je veux que chaque ligne tombe sous deux horodatages. Ma structure de journal ressemble à ceci:

[2014-04-07 23:59:58] CheckForCallAction [ERROR] Exception caught in +CheckForCallAction :: null
--Checking user--
Post
[2014-04-08 00:00:03] MobileAppRequestFilter [DEBUG] Action requested checkforcall

Supposons que je veuille extraire tout entre 2014-04-07 23:00et 2014-04-08 02:00.

Veuillez noter que l'horodatage de début ou l'horodatage de fin peuvent ne pas être présents dans le journal, mais je veux que chaque ligne entre ces deux horodatages.

Amit
la source
Copie possible de stackoverflow.com/questions/7575267/…
Ramesh
Avez-vous juste besoin de le faire une seule fois ou à plusieurs reprises par programme?
Bratchley
La raison pour laquelle je demande est parce que vous pouvez faire deux grep contextuels (un pour tout récupérer après le délimiteur de départ et un autre pour arrêter l'impression au délimiteur de fin) si vous connaissez les valeurs littérales. Si les dates / heures peuvent changer, vous pouvez facilement les générer à la volée en alimentant l'utilisateur par la date -dcommande et en l'utilisant pour construire le modèle de recherche.
Bratchley
@Ramesh, la question référencée est trop large.
maxschlepzig
@JoelDavis: Je veux le faire par programme. Donc, à chaque fois, j'ai juste besoin d'entrer l'horodatage souhaité pour extraire les journaux entre ces horodatages à mon emplacement / tmp.
Amit

Réponses:

19

Vous pouvez utiliser awkpour cela:

$ awk -F'[]]|[[]' \
  '$0 ~ /^\[/ && $2 >= "2014-04-07 23:00" { p=1 }
   $0 ~ /^\[/ && $2 >= "2014-04-08 02:00" { p=0 }
                                        p { print $0 }' log

Où:

  • -Fspécifie les caractères [et ]comme séparateurs de champ à l'aide d'une expression régulière
  • $0 référence une ligne complète
  • $2 fait référence au champ de date
  • p est utilisé comme variable booléenne qui protège l'impression réelle
  • $0 ~ /regex/ est vrai si regex correspond $0
  • >=est utilisé pour comparer lexicographiquement une chaîne (équivalent à par exemple strcmp())

Variations

La ligne de commande ci-dessus implémente la correspondance d' intervalle de temps d'ouverture à droite . Pour obtenir une sémantique d'intervalle fermé, il suffit d'incrémenter votre bonne date, par exemple:

$ awk -F'[]]|[[]' \
  '$0 ~ /^\[/ && $2 >= "2014-04-07 23:00"    { p=1 }
   $0 ~ /^\[/ && $2 >= "2014-04-08 02:00:01" { p=0 }
                                           p { print $0 }' log

Si vous souhaitez faire correspondre des horodatages dans un autre format, vous devez modifier la $0 ~ /^\[/sous-expression. Notez qu'il ignorait les lignes sans horodatage de la logique d'impression activée / désactivée.

Par exemple, pour un format d'horodatage comme YYYY-MM-DD HH24:MI:SS(sans []accolades), vous pouvez modifier la commande comme ceci:

$ awk \
  '$0 ~ /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-2][0-9]:[0-5][0-9]:[0-5][0-9]/
      {
        if ($1" "$2 >= "2014-04-07 23:00")     p=1;
        if ($1" "$2 >= "2014-04-08 02:00:01")  p=0;
      }
    p { print $0 }' log

(notez que le séparateur de champs est également modifié - en transition vide / non vide, la valeur par défaut)

maxschlepzig
la source
Merci d'avoir partagé le script mais il ne vérifie pas l'horodatage de fin. Pouvez-vous vérifier. Faites-moi également savoir si j'ai les journaux comme 2014-04-07 23:59:58. Je veux dire sans accolades
Amit
@Amit, a mis à jour la réponse
maxschlepzig
Bien que je ne pense pas que ce soit un problème de chaîne (voir ma réponse ), vous pourriez rendre le vôtre beaucoup plus lisible, et probablement un peu plus rapide, en ne répétant pas tous les tests: $1 ~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}/ && $2 ~/[0-2][0-9]:[0-5][0-9]:[0-5][0-9]/ { Time = $1" "$2; if (Time >= "2014-04-07 23:00" ) { p=1 } if (Time >= "2014-04-08 02:00:01" ) { p=0 } } p
Salut Max, Un petit doute de plus .. Si j'ai quelque chose comme Apr-07-2014 10:51:17. Alors quels changements je dois faire .. J'ai essayé code$ 0 ~ / ^ [az | AZ] {4} - [0-9] {2} - [0-9] {4} [0-2] [0-9 ]: [0-5] [0-9]: [0-5] [0-9] / && $ 1 "" $ 2> = "Apr-07-2014 11:00" {p = 1} $ 0 ~ / ^ [az | AZ] {4} - [0-9] {2} - [0-9] {4} [0-2] [0-9]: [0-5] [0-9]: [0 -5] [0-9] / && $ 1 "" $ 2> = "Apr-07-2014 12:00:01" {p = 0} codemais cela ne fonctionne pas
Modifier le
@awk_FTW, a changé le code de telle sorte que l'expression régulière est explicitement partagée.
maxschlepzig
12

Découvrez dategrepà https://github.com/mdom/dategrep

La description:

dategrep recherche dans les fichiers d'entrée nommés les lignes correspondant à une plage de dates et les imprime sur stdout.

Si dategrep fonctionne sur un fichier recherché, il peut effectuer une recherche binaire pour trouver la première et la dernière ligne à imprimer assez efficacement. dategrep peut également lire à partir de stdin si l'un des arguments de nom de fichier n'est qu'un trait d'union, mais dans ce cas, il doit analyser chaque ligne qui sera plus lente.

Exemples d'utilisation:

dategrep --start "12:00" --end "12:15" --format "%b %d %H:%M:%S" syslog
dategrep --end "12:15" --format "%b %d %H:%M:%S" syslog
dategrep --last-minutes 5 --format "%b %d %H:%M:%S" syslog
dategrep --last-minutes 5 --format rsyslog syslog
cat syslog | dategrep --end "12:15" -

Bien que cette limitation puisse rendre cela inapproprié pour votre question exacte:

Pour le moment, dategrep mourra dès qu'il trouvera une ligne qui n'est pas analysable. Dans une future version, cela sera configurable.

cpugeniusmv
la source
J'ai entendu parler de cette commande il y a seulement quelques jours grâce à onethingwell.org/post/81991115668/dategrep donc, bravo à lui!
cpugeniusmv
3

Une alternative awkou un outil non standard consiste à utiliser GNU greppour ses greps contextuels. GNU grepvous permettra de spécifier le nombre de lignes après une correspondance positive avec laquelle imprimer -Aet les lignes précédentes avec lesquelles imprimer -BPar exemple:

[davisja5@xxxxxxlp01 ~]$ cat test.txt
Ignore this line, please.
This one too while you're at it...
[2014-04-07 23:59:58] CheckForCallAction [ERROR] Exception caught in +CheckForCallAction :: null
--Checking user--
Post
[2014-04-08 00:00:03] MobileAppRequestFilter [DEBUG] Action requested checkforcall
we don't
want these lines.


[davisja5@xxxxxxlp01 ~]$ egrep "^\[2014-04-07 23:59:58\]" test.txt -A 10000 | egrep "^\[2014-04-08 00:00:03\]" -B 10000
[2014-04-07 23:59:58] CheckForCallAction [ERROR] Exception caught in +CheckForCallAction :: null
--Checking user--
Post
[2014-04-08 00:00:03] MobileAppRequestFilter [DEBUG] Action requested checkforcall

Ce qui précède indique essentiellement grepd'imprimer les 10 000 lignes qui suivent la ligne qui correspond au modèle auquel vous souhaitez commencer, ce qui fait que votre sortie commence là où vous le souhaitez et va jusqu'à la fin (avec un peu de chance) tandis que la seconde egrepdans le pipeline lui indique de n'imprimer que la ligne avec le délimiteur de fin et les 10 000 lignes devant elle. Le résultat final de ces deux est de commencer où vous voulez et de ne pas aller là où vous lui avez dit d'arrêter.

10000 est juste un nombre que j'ai trouvé, n'hésitez pas à le changer en un million si vous pensez que votre sortie va être trop longue.

Bratchley
la source
Comment cela fonctionnera-t-il s'il n'y a pas d'entrée de journal pour les plages de début et de fin? Si OP veut tout entre 14h00 et 15h00, mais qu'il n'y a pas d'entrée de journal pour 14h00, alors?
Il contiendra ainsi que le sedqui recherche également des correspondances littérales. dategrepest probablement la réponse la plus correcte de toutes celles données (car vous devez être en mesure de vous rendre "flou" sur les horodatages que vous accepterez) mais comme la réponse le dit, je le mentionnais simplement comme une alternative. Cela dit, si le journal est suffisamment actif pour générer suffisamment de sortie pour justifier la coupe, il y aura probablement aussi une sorte d'entrée pour la période donnée.
Bratchley
0

Utilisation de sed:

#!/bin/bash

E_BADARGS=23

if [ $# -ne "3" ]
then
  echo "Usage: `basename $0` \"<start_date>\" \"<end_date>\" file"
  echo "NOTE:Make sure to put dates in between double quotes"
  exit $E_BADARGS
fi 

isDatePresent(){
        #check if given date exists in file.
        local date=$1
        local file=$2
        grep -q "$date" "$file"
        return $?

}

convertToEpoch(){
    #converts to epoch time
    local _date=$1
    local epoch_date=`date --date="$_date" +%s`
    echo $epoch_date
}

convertFromEpoch(){
    #converts to date/time format from epoch
    local epoch_date=$1
    local _date=`date  --date="@$epoch_date" +"%F %T"`
    echo $_date

}

getDates(){
        # collects all dates at beginning of lines in a file, converts them to epoch and returns a sequence of numbers
        local file="$1"
        local state="$2"
        local i=0
        local date_array=( )
        if [[ "$state" -eq "S" ]];then
            datelist=`cat "$file" | sed -r -e "s/^\[([^\[]+)\].*/\1/" | egrep  "^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}"`
        elif [[ "$state" -eq "E" ]];then
            datelist=`tac "$file" | sed -r -e "s/^\[([^\[]+)\].*/\1/" | egrep  "^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}"`

        else
            echo "Something went wrong while getting dates..." 1>&2
            exit 500
        fi

        while read _date
            do
                epoch_date=`convertToEpoch "$_date"`
                date_array[$i]=$epoch_date
                #echo "$_date" "$epoch_date" 1>&2

            (( i++ ))
            done<<<"$datelist"
        echo ${date_array[@]}   


}

findneighbours(){
    # search next best date if date is not in the file using recursivity
    IFS="$old_IFS"
    local elt=$1
    shift
    local state="$1"
    shift
    local -a array=( "$@" ) 

    index_pivot=`expr ${#array[@]} / 2`
    echo "#array="${#array[@]} ";array="${array[@]} ";index_pivot="$index_pivot 1>&2
    if [ "$index_pivot" -eq 1 -a ${#array[@]} -eq 2 ];then

        if [ "$state" == "E" ];then
            echo ${array[0]}
        elif [ "$state" == "S" ];then
            echo ${array[(( ${#array[@]} - 1 ))]} 
        else
            echo "State" $state "undefined" 1>&2
            exit 100
        fi

    else
        echo "elt with index_pivot="$index_pivot":"${array[$index_pivot]} 1>&2
        if [ $elt -lt ${array[$index_pivot]} ];then
            echo "elt is smaller than pivot" 1>&2
            array=( ${array[@]:0:(($index_pivot + 1)) } )
        else
            echo "elt is bigger than pivot" 1>&2
            array=( ${array[@]:$index_pivot:(( ${#array[@]} - 1 ))} ) 
        fi
        findneighbours "$elt" "$state" "${array[@]}"
    fi
}



findFirstDate(){
    local file="$1"
    echo "Looking for first date in file" 1>&2
    while read line
        do 
            echo "$line" | egrep -q "^\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\]" &>/dev/null
            if [ "$?" -eq "0" ]
            then
                #echo "line=" "$line" 1>&2
                firstdate=`echo "$line" | sed -r -e "s/^\[([^\[]+)\].*/\1/"`
                echo "$firstdate"
                break
            else
                echo $? 1>&2
            fi
        done< <( cat "$file" )



}

findLastDate(){
    local file="$1"
    echo "Looking for last date in file" 1>&2
    while read line
        do 
            echo "$line" | egrep -q "^\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\]" &>/dev/null
            if [ "$?" -eq "0" ]
            then
                #echo "line=" "$line" 1>&2
                lastdate=`echo "$line" | sed -r -e "s/^\[([^\[]+)\].*/\1/"`
                echo "$lastdate"
                break
            else
                echo $? 1>&2
            fi
        done< <( tac "$file" )


}

findBestDate(){

        IFS="$old_IFS"
        local initdate="$1"
        local file="$2"
        local state="$3"
        local first_elts="$4"
        local last_elts="$5"
        local date_array=( )
        local initdate_epoch=`convertToEpoch "$initdate"`   

        if [[ $initdate_epoch -lt $first_elt ]];then
            echo `convertFromEpoch "$first_elt"`
        elif [[ $initdate_epoch -gt $last_elt ]];then
            echo `convertFromEpoch "$last_elt"` 

        else
            date_array=( `getDates "$file" "$state"` )
            echo "date_array="${date_array[@]} 1>&2
            #first_elt=${date_array[0]}
            #last_elt=${date_array[(( ${#date_array[@]} - 1 ))]}

            echo `convertFromEpoch $(findneighbours "$initdate_epoch" "$state" "${date_array[@]}")`

        fi

}


main(){
    init_date_start="$1"
    init_date_end="$2"
    filename="$3"
    echo "problem start.." 1>&2
    date_array=( "$init_date_start","$init_date_end"  )
    flag_array=( 0 0 )
    i=0
    #echo "$IFS" | cat -vte
    old_IFS="$IFS"
    #changing separator to avoid whitespace issue in date/time format
    IFS=,
    for _date in ${date_array[@]}
    do
        #IFS="$old_IFS"
        #echo "$IFS" | cat -vte
        if isDatePresent "$_date" "$filename";then
            if [ "$i" -eq 0 ];then 
                echo "Starting date exists" 1>&2
                #echo "date_start=""$_date" 1>&2
                date_start="$_date"
            else
                echo "Ending date exists" 1>&2
                #echo "date_end=""$_date" 1>&2
                date_end="$_date"
            fi

        else
            if [ "$i" -eq 0 ];then 
                echo "start date $_date not found" 1>&2
            else
                echo "end date $_date not found" 1>&2
            fi
            flag_array[$i]=1
        fi
        #IFS=,
        (( i++ ))
    done

    IFS="$old_IFS"
    if [ ${flag_array[0]} -eq 1 -o ${flag_array[1]} -eq 1 ];then

        first_elt=`convertToEpoch "$(findFirstDate "$filename")"`
        last_elt=`convertToEpoch "$(findLastDate "$filename")"`
        border_dates_array=( "$first_elt","$last_elt" )

        #echo "first_elt=" $first_elt "last_elt=" $last_elt 1>&2
        i=0
        IFS=,
        for _date in ${date_array[@]}
        do
            if [ $i -eq 0 -a ${flag_array[$i]} -eq 1 ];then
                date_start=`findBestDate "$_date" "$filename" "S" "${border_dates_array[@]}"`
            elif [ $i -eq 1 -a ${flag_array[$i]} -eq 1 ];then
                date_end=`findBestDate "$_date" "$filename" "E" "${border_dates_array[@]}"`
            fi

            (( i++ ))
        done
    fi


    sed -r -n "/^\[${date_start}\]/,/^\[${date_end}\]/p" "$filename"

}


main "$1" "$2" "$3"

Copiez ceci dans un fichier. Si vous ne voulez pas voir les informations de débogage, le débogage est envoyé à stderr alors ajoutez simplement "2> / dev / null"

UnX
la source
1
Cela n'affichera pas les fichiers journaux qui n'ont pas d'horodatage.
Amit
@Amit, oui, vous l'avez essayé?
UnX
@rMistero, cela ne fonctionnera pas car s'il n'y a pas d'entrée de journal à 22h30, la plage ne sera pas terminée. Comme OP l'a mentionné, les heures de début et de fin peuvent ne pas figurer dans les journaux. Vous pouvez modifier votre regex pour qu'il fonctionne, mais vous perdrez la résolution et ne serez jamais garanti à l'avance que la plage se terminera au bon moment.
@awk_FTW c'était un exemple, je n'ai pas utilisé les horodatages fournis par Amit. Encore une fois, l'expression régulière peut être utilisée. Je suis d'accord pour dire que cela ne fonctionnera pas si l'horodatage n'existe pas lorsqu'il est fourni explicitement ou qu'aucune expression régulière d'horodatage ne correspond. Je vais l'améliorer bientôt ..
UnX
"Comme OP l'a mentionné, les heures de début et de fin peuvent ne pas figurer dans les journaux." Non, relisez l'OP. OP dit que ces seront présents mais les lignes intermédiaires ne commenceront pas nécessairement par un horodatage. Cela n'a même pas de sens de dire que les heures d'arrêt pourraient ne pas être présentes. Comment pourriez-vous dire à un outil où s'arrêter si le marqueur de terminaison n'est pas garanti d'être là? Il n'y aurait aucun critère permettant à l'outil de lui indiquer où arrêter le traitement.
Bratchley