web-dev-qa-db-fra.com

Comment extraire des journaux entre deux horodatages

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:00 et 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 chaque ligne entre ces deux horodatages.

25
Amit

Vous pouvez utiliser awk pour 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ù:

  • -F Spécifie les caractères [ Et ] Comme séparateurs de champ à l'aide d'une expression régulière
  • $0 Fait 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 l'expression régulière correspond à $0
  • >= Est utilisé pour comparer lexicographiquement une chaîne (équivalent à par exemple strcmp())

Variations

La ligne de commande ci-dessus implémente intervalle de temps ouvert à droite correspondance. Pour obtenir une sémantique d'intervalle fermé, augmentez simplement 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 sous-expression $0 ~ /^\[/. 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)

19
maxschlepzig

Consultez dategrep sur 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 depuis 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.

12
cpugeniusmv

Une alternative à awk ou à un outil non standard consiste à utiliser GNU grep pour ses greps contextuels. GNU's grep vous permettra de spécifier le nombre de lignes après une correspondance positive à imprimer avec -A et les lignes précédentes pour imprimer avec -B Par 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 dit essentiellement à grep d'imprimer les 10 000 lignes qui suivent la ligne qui correspond au modèle sur lequel vous souhaitez commencer, ce qui fait que votre sortie commence là où vous le souhaitez et va jusqu'à la fin (espérons ) alors que le second egrep dans le pipeline lui indique d'imprimer uniquement la ligne avec le délimiteur de fin et les 10 000 lignes qui le précèdent. 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 n'est qu'un chiffre que j'ai trouvé, n'hésitez pas à le changer en un million si vous pensez que votre sortie va être trop longue.

3
Bratchley

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"

0
UnX