Jak wyodrębnić dzienniki między dwoma znacznikami czasu

25

Chcę wyodrębnić wszystkie dzienniki między dwoma znacznikami czasu. Niektóre linie mogą nie mieć znacznika czasu, ale chcę też te linie. Krótko mówiąc, chcę, aby każda linia mieściła się w dwóch znacznikach czasu. Moja struktura dziennika wygląda następująco:

[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

Załóżmy, że chcę wyodrębnić wszystko między 2014-04-07 23:00i 2014-04-08 02:00.

Pamiętaj, że w dzienniku może nie być znacznika czasu rozpoczęcia lub zakończenia, ale chcę mieć każdą linię między tymi dwoma znacznikami czasu.

Amit
źródło
Możliwy duplikat stackoverflow.com/questions/7575267/…
Ramesh
Czy musisz to zrobić tylko raz lub programowo w różnych momentach?
Bratchley,
Powód, dla którego pytam, jest taki, że możesz wykonać dwa kontekstowe grep'y (jeden, aby pobrać wszystko po początkowym ograniczniku, a drugi, aby zatrzymać drukowanie na ograniczniku końcowym), jeśli znasz wartości literalne. Jeśli daty / godziny mogą ulec zmianie, tou może z łatwością generować je w locie, przesyłając dane wejściowe użytkownika za pomocą date -dpolecenia i wykorzystując je do skonstruowania wzorca wyszukiwania.
Bratchley,
@Ramesh, przywołane pytanie jest zbyt ogólne.
maxschlepzig
@JelDavis: Chcę to zrobić programowo. Więc za każdym razem po prostu muszę wprowadzić żądany znacznik czasu, aby wyodrębnić dzienniki między tymi znacznikami czasu w mojej lokalizacji / tmp.
Amit

Odpowiedzi:

19

Możesz użyć awkdo tego:

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

Gdzie:

  • -Fokreśla znaki [i ]separatory pól za pomocą wyrażeń regularnych
  • $0 odwołuje się do pełnej linii
  • $2 odwołuje się do pola daty
  • p jest używany jako zmienna logiczna, która chroni rzeczywiste drukowanie
  • $0 ~ /regex/ jest prawdziwe, jeśli wyrażenie regularne pasuje $0
  • >=służy do leksykograficznego porównywania łańcucha (odpowiednik np. strcmp())

Wariacje

Powyższy wiersz poleceń implementuje dopasowanie przedziału czasu po prawej stronie . Aby uzyskać semantykę zamkniętych interwałów, po prostu zwiększ swoją właściwą datę, np .:

$ 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

Jeśli chcesz dopasować znaczniki czasu w innym formacie, musisz zmodyfikować $0 ~ /^\[/podwyrażenie. Zauważ, że kiedyś ignorowało linie bez żadnych znaczników czasu z logiki drukowania / wyłączania.

Na przykład dla formatu znacznika czasu, takiego jak YYYY-MM-DD HH24:MI:SS(bez []nawiasów klamrowych), możesz zmodyfikować polecenie w następujący sposób:

$ 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

(zwróć uwagę, że zmieniany jest również separator pól - domyślnie przejście na puste / niepuste)

maxschlepzig
źródło
Dziękujemy za udostępnienie skryptu, ale nie sprawdza on znacznika czasu zakończenia. Czy możesz to sprawdzić. Daj mi również znać, co jeśli będę mieć dzienniki takie jak 2014-04-07 23:59:58. Mam na myśli bez aparatu ortodontycznego
Amit
@Amit, zaktualizowałem odpowiedź
maxschlepzig
Chociaż nie sądzę, że jest to problem z ciągiem znaków (patrz moja odpowiedź ), możesz uczynić swój o wiele bardziej czytelnym i prawdopodobnie nieco szybszym, nie powtarzając wszystkich testów: $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
Cześć Max, Jeszcze jedna mała wątpliwość .. Jeśli mam coś w stylu Apr-07-2014 10:51:17. Więc jakie zmiany muszę zrobić .. Próbowałem code0 ~ / ^ [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}, codeale nie działa
Amit
@awk_FTW, zmieniłem kod tak, że wyrażenie regularne jest jawnie udostępnione.
maxschlepzig
12

Sprawdź dategrepna https://github.com/mdom/dategrep

Opis:

dategrep przeszukuje nazwane pliki wejściowe w poszukiwaniu linii pasujących do zakresu dat i wypisuje je na standardowe wyjście.

Jeśli dategrep działa na widocznym pliku, może przeprowadzić wyszukiwanie binarne, aby znaleźć pierwszą i ostatnią linię, aby wydrukować dość wydajnie. dategrep może również czytać ze standardowego wejścia, jeśli argumentem nazwy pliku jest tylko myślnik, ale w tym przypadku musi przeanalizować każdą linię, która będzie wolniejsza.

Przykłady użycia:

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

Chociaż to ograniczenie może sprawić, że nie będzie to właściwe dla twojego dokładnego pytania:

W tej chwili dategrep umrze, jak tylko znajdzie linię, której nie można przeanalizować. W przyszłej wersji będzie to możliwe do skonfigurowania.

cpugeniusmv
źródło
Dowiedziałem się o tym poleceniu zaledwie kilka dni temu dzięki uprzejmości onethingwell.org/post/81991115668/dategrep, więc chwała mu!
cpugeniusmv
3

Jedną alternatywą awklub niestandardowym narzędziem jest użycie GNU grepdla jego kontekstowych greps. GNU greppozwoli ci określić liczbę linii po pozytywnym dopasowaniu do wydrukowania -Ai poprzednie linie do wydrukowania -Bna przykład:

[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

Powyższe zasadniczo mówi grepo wydrukowaniu 10 000 linii następujących po linii, która odpowiada wzorowi, od którego chcesz zacząć, skutecznie powodując, że twój wydruk zaczyna się tam, gdzie chcesz i trwa do końca (miejmy nadzieję), podczas gdy drugi egrepw potok mówi mu, aby drukował tylko linię z ogranicznikiem końcowym i 10 000 linii przed nią. Końcowym rezultatem tych dwóch czynności jest rozpoczęcie tam, gdzie chcesz, a nie przejście do miejsca, w którym kazałeś to zatrzymać.

10 000 to tylko liczba, którą wymyśliłem, możesz ją zmienić na milion, jeśli uważasz, że Twoje wyniki będą zbyt długie.

Bratchley
źródło
Jak to będzie działać, jeśli nie będzie wpisu dziennika dla zakresu początkowego i końcowego? Jeśli OP chce wszystkiego między 14:00 a 15:00, ale nie ma wpisu dziennika na 14:00, to?
Będzie zawierał informacje o tym, sedco również szuka dosłownych dopasowań. dategrepjest prawdopodobnie najbardziej poprawną odpowiedzią ze wszystkich podanych (ponieważ musisz być w stanie „rozmyć”, jakie znaczniki czasu akceptujesz), ale jak mówi odpowiedź, właśnie wspomniałem o tym jako alternatywę. To powiedziawszy, jeśli dziennik jest wystarczająco aktywny, aby wygenerować wystarczającą moc wyjściową, aby uzasadnić cięcie, prawdopodobnie będzie miał również jakiś wpis dla danego okresu czasu.
Bratchley,
0

Za pomocą 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"

Skopiuj to do pliku. Jeśli nie chcesz widzieć informacji o debugowaniu, debugowanie jest wysyłane do stderr, więc po prostu dodaj „2> / dev / null”

UnX
źródło
1
Nie wyświetla plików dziennika, które nie mają znacznika czasu.
Amit
@Amit, tak, spróbowałeś?
UnX
@rMistero, to nie zadziała, ponieważ jeśli nie ma wpisu w dzienniku o 22:30, zakres nie zostanie zakończony. Jak wspomniano OP, czasy rozpoczęcia i zakończenia mogą nie być zapisane w logach. Można dostosować swój regex do jego pracy, ale można stracić rozdzielczość i nigdy nie może być zagwarantowane z góry , że zakres zakończy we właściwym czasie.
@awk_FTW to był przykład, nie użyłem znaczników czasu dostarczonych przez Amit. Ponownie można użyć wyrażenia regularnego. Zgadzam się, że nie zadziała, jeśli znacznik czasu nie istnieje, jeśli zostanie podany jawnie, lub jeśli nie będzie pasujących wyrażeń regularnych. Wkrótce go
poprawię
„Jak wspomniano OP, czasy rozpoczęcia i zakończenia mogą nie znajdować się w dziennikach.” Nie, przeczytaj PO ponownie. OP twierdzi, że BĘDĄ one obecne, ale interweniujące linie niekoniecznie zaczną się od znacznika czasu. Nie ma nawet sensu stwierdzenie, że czasy zatrzymania mogą nie być obecne. Jak mógłbyś kiedykolwiek powiedzieć dowolnemu narzędziu, gdzie ma się zatrzymać, jeśli nie ma gwarancji, że będzie tam znacznik zakończenia? Nie byłoby żadnych kryteriów, które dawałyby narzędziu informację, gdzie przerwać przetwarzanie.
Bratchley,