So extrahieren Sie Protokolle zwischen zwei Zeitstempeln


25

Ich möchte alle Protokolle zwischen zwei Zeitstempeln extrahieren. Einige Zeilen haben möglicherweise nicht den Zeitstempel, aber ich möchte diese Zeilen auch. Kurz gesagt, ich möchte jede Zeile, die unter zwei Zeitstempel fällt. Meine Logstruktur sieht so aus:

[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

Angenommen, ich möchte alles zwischen 2014-04-07 23:00und extrahieren 2014-04-08 02:00.

Bitte beachten Sie, dass der Start- oder Endzeitstempel möglicherweise nicht im Protokoll enthalten ist, ich jedoch jede Zeile zwischen diesen beiden Zeitstempeln verwenden möchte.



Müssen Sie dies nur einmal oder programmgesteuert zu verschiedenen Zeiten tun?
Bratchley

Der Grund, den ich frage, ist, dass Sie zwei kontextbezogene Greps ausführen können (eines, um alles nach dem Startbegrenzer zu erfassen, und eines, um das Drucken am Endbegrenzer zu beenden), wenn Sie die Literalwerte kennen. Wenn sich Datum und Uhrzeit ändern können, können Sie diese einfach im laufenden Betrieb generieren, indem Sie Benutzereingaben über den date -dBefehl eingeben und damit das Suchmuster erstellen.
Bratchley

@Ramesh, die Frage, auf die verwiesen wird, ist zu weit gefasst.
Maxschlepzig

@ JoelDavis: Ich möchte es programmatisch tun. Also muss ich jedes Mal nur den gewünschten Zeitstempel eingeben, um die Protokolle zwischen diesen Zeitstempeln in meinem / tmp-Speicherort zu extrahieren.
Amit

Antworten:


19

Sie können dafür verwenden awk:

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

Woher:

  • -F legt die Zeichen fest [ und ]als Feldtrennzeichen mit einem regulären Ausdruck fest
  • $0 referenziert eine komplette Zeile
  • $2 verweist auf das Datumsfeld
  • p wird als boolesche Variable verwendet, die den tatsächlichen Druckvorgang überwacht
  • $0 ~ /regex/ ist wahr, wenn Regex übereinstimmt $0
  • >=wird zum lexikografischen Vergleichen von Strings verwendet (entspricht zB strcmp())

Variationen

Die obige Befehlszeile implementiert einen rechtsoffenen Zeitintervallabgleich . Um eine Semantik für geschlossene Intervalle zu erhalten, erhöhen Sie einfach Ihr rechtes Datum, zB:

$ 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

Wenn Sie Zeitstempel in einem anderen Format abgleichen möchten, müssen Sie das ändern $0 ~ /^\[/ Unterausdruck . Beachten Sie, dass es verwendet wird, um Zeilen ohne Zeitstempel aus der Druck-Ein / Aus-Logik zu ignorieren.

Beispielsweise können Sie für ein Zeitstempelformat wie YYYY-MM-DD HH24:MI:SS(ohne []geschweifte Klammern) den Befehl folgendermaßen ändern:

$ 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

(Beachten Sie, dass auch das Feldtrennzeichen geändert wird - auf leer / nicht leer Übergang, die Standardeinstellung)


Vielen Dank für das Teilen des Skripts, aber es überprüft nicht den Endzeitstempel. Können Sie das bitte überprüfen. Lassen Sie mich auch wissen, was, wenn ich die Protokolle wie 2014-04-07 23:59:58 habe. Ich meine ohne geschweifte Klammern
Amit

@Amit, hat die Antwort aktualisiert
maxschlepzig

Obwohl ich nicht denke, dass dies ein String-Problem ist (siehe meine Antwort ), könnten Sie Ihr Problem viel lesbarer und wahrscheinlich ein bisschen schneller machen, indem Sie nicht alle Tests wiederholen: $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

Hallo Max, noch ein kleiner Zweifel. Wenn ich so etwas wie Apr-07-2014 10:51:17 habe. Was muss ich dann ändern code? Ich habe versucht, $ 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}, codeaber es funktioniert nicht
Amit

@awk_FTW, hat den Code so geändert, dass der reguläre Ausdruck explizit freigegeben wird.
Maxschlepzig

12

Check out dategrepunter https://github.com/mdom/dategrep

Beschreibung:

dategrep durchsucht die benannten Eingabedateien nach Zeilen, die einem Datumsbereich entsprechen, und druckt sie auf stdout aus.

Wenn dategrep an einer durchsuchbaren Datei arbeitet, kann es eine binäre Suche durchführen, um die erste und letzte zu druckende Zeile ziemlich effizient zu finden. dategrep kann auch von stdin lesen, wenn eines der Dateinamenargumente nur ein Bindestrich ist, aber in diesem Fall muss es jede einzelne Zeile analysieren, die langsamer sein wird.

Anwendungsbeispiele:

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" -

Obwohl diese Einschränkung dies für Ihre genaue Frage möglicherweise ungeeignet macht:

Im Moment stirbt dategrep, sobald eine Zeile gefunden wird, die nicht syntaktisch analysiert werden kann. In einer zukünftigen Version wird dies konfigurierbar sein.


Ich habe diesen Befehl erst vor ein paar Tagen mit freundlicher Genehmigung von onethingwell.org/post/81991115668/dategrep erfahren, also ein großes Lob an ihn!
Cpugeniusmv

3

Eine Alternative awkoder ein nicht standardmäßiges Tool ist die Verwendung von GNU grepfür seine kontextbezogenen Greps. Mit GNU grepkönnen Sie die Anzahl der Zeilen nach einer positiven Übereinstimmung, mit -Ader gedruckt werden soll, und die Anzahl der vorhergehenden Zeilen, mit denen gedruckt werden soll, angeben. -BBeispiel:

[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

Die oben erzählt im Wesentlichen grepdie 10.000 Zeilen zu drucken , die der Linie folgen , die das Muster übereinstimmt Sie wollen an beginnen, effektiv Ihre Ausgabe zu machen beginnen , wo Sie es und geht bis zum Ende (hoffentlich) , während der zweiten wollen , sind egrepin der Die Pipeline weist sie an, nur die Zeile mit dem Endbegrenzer und den 10.000 Zeilen davor zu drucken. Das Endresultat dieser beiden beginnt dort, wo Sie wollen, und geht nicht dort vorbei, wo Sie es befohlen haben, damit aufzuhören.

10.000 ist nur eine Zahl, die ich mir ausgedacht habe. Sie können sie jederzeit in eine Million ändern, wenn Sie der Meinung sind, dass Ihre Ausgabe zu lang sein wird.


Wie funktioniert das, wenn für den Start- und Endbereich kein Protokolleintrag vorhanden ist? Wenn OP alles zwischen 14:00 und 15:00 will, aber für 14:00 keinen Protokolleintrag gibt, dann?

Es wird sowohl darüber als auch darüber gesprochen, sedwas auch nach wörtlichen Übereinstimmungen sucht. dategrepist wahrscheinlich die richtigste Antwort von allen gegebenen (da Sie in der Lage sein müssen, "unscharf" zu werden, welche Zeitstempel Sie akzeptieren), aber wie die Antwort sagt, habe ich es nur als Alternative erwähnt. Das heißt, wenn das Protokoll aktiv genug ist genug Ausgabe zu rechtfertigen generieren Schneiden es wahrscheinlich auch gehen , um haben einige für die gegebene Zeitfenster Art des Eintrages.
Bratchley

0

Sed verwenden:

#!/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"

Kopieren Sie dies in eine Datei. Wenn Sie keine Debugging-Informationen sehen möchten, wird das Debugging an stderr gesendet. Fügen Sie einfach "2> / dev / null" hinzu.


1
Dadurch werden keine Protokolldateien angezeigt, die keinen Zeitstempel haben.
Amit

@Amit, ja, hast du es versucht?
UnX

@rMistero, es wird nicht funktionieren, da der Bereich nicht beendet wird, wenn um 22:30 kein Protokolleintrag vorhanden ist. Wie OP erwähnt, sind die Start- und Stoppzeiten möglicherweise nicht in den Protokollen enthalten. Sie können den regulären Ausdruck so anpassen, dass er funktioniert. Sie verlieren jedoch die Auflösung und können nicht im Voraus garantieren , dass der Bereich zum richtigen Zeitpunkt endet.

@awk_FTW Dies war ein Beispiel, ich habe die von Amit bereitgestellten Zeitstempel nicht verwendet. Auch hier kann Regex verwendet werden. Ich bin damit einverstanden, dass es nicht funktioniert, wenn der Zeitstempel nicht vorhanden ist, wenn er explizit angegeben wird, oder wenn kein Zeitstempel mit dem regulären Ausdruck übereinstimmt. Ich werde es bald verbessern ..
UnX

"Wie in OP erwähnt, befinden sich die Start- und Stoppzeiten möglicherweise nicht in den Protokollen." Nein, lesen Sie das OP noch einmal. OP sagt, dass diese vorhanden sein werden, aber dazwischenliegende Zeilen werden nicht unbedingt mit einem Zeitstempel beginnen. Es ist nicht einmal sinnvoll zu sagen, dass die Stoppzeiten möglicherweise nicht vorhanden sind. Wie können Sie einem Tool jemals mitteilen , wo es anhalten soll, wenn nicht garantiert ist, dass der Beendigungsmarker vorhanden ist? Es gibt keine Kriterien, anhand derer das Tool erkennen kann, wo die Verarbeitung beendet werden soll.
Bratchley
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.