Come estrarre i registri tra due timestamp


25

Voglio estrarre tutti i registri tra due timestamp. Alcune righe potrebbero non avere il timestamp, ma voglio anche quelle righe. In breve, voglio ogni riga che rientri in due timestamp. La mia struttura del registro è simile a:

[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

Supponiamo che io voglia estrarre tutto tra 2014-04-07 23:00e 2014-04-08 02:00.

Si noti che il timestamp di inizio o timestamp di fine potrebbe non essere presente nel registro, ma voglio ogni riga tra questi due timestamp.



Hai solo bisogno di farlo solo una volta o programmaticamente in varie occasioni?
Bratchley,

Il motivo per cui ti chiedo è perché puoi fare due grep contestuali (uno per prendere tutto dopo il delimitatore iniziale e un altro per interrompere la stampa sul delimitatore finale) se conosci i valori letterali. Se le date / gli orari possono cambiare, puoi facilmente generarli al volo alimentando l'input dell'utente tramite il date -dcomando e utilizzandolo per costruire il modello di ricerca.
Bratchley,

@Ramesh, la domanda di riferimento è troppo ampia.
maxschlepzig,

@JoelDavis: voglio farlo a livello di programmazione. Quindi ogni volta che devo solo inserire il timestamp desiderato per estrarre i registri tra quei timestamp nella mia posizione / tmp.
Tra il

Risposte:


19

Puoi usare awkper questo:

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

Dove:

  • -Fspecifica i caratteri [e ]come separatori di campo usando un'espressione regolare
  • $0 fa riferimento a una riga completa
  • $2 fa riferimento al campo della data
  • p viene utilizzato come variabile booleana che protegge la stampa effettiva
  • $0 ~ /regex/ è vero se regex corrisponde $0
  • >=viene utilizzato per il confronto lessicografico di stringhe (equivalente ad es. strcmp())

variazioni

La riga di comando sopra implementa la corrispondenza dell'intervallo di tempo con apertura a destra . Per ottenere la semantica dell'intervallo chiuso, basta aumentare la data corretta, ad esempio:

$ 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

Nel caso in cui si desideri abbinare i timestamp in un altro formato, è necessario modificare la $0 ~ /^\[/sottoespressione. Si noti che è stato utilizzato per ignorare le linee senza timestamp dalla logica di attivazione / disattivazione della stampa.

Ad esempio per un formato timestamp come YYYY-MM-DD HH24:MI:SS(senza []parentesi graffe) è possibile modificare il comando in questo modo:

$ 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

(nota che anche il separatore di campo è cambiato - in transizione vuota / non vuota, l'impostazione predefinita)


Grazie per aver condiviso lo script ma non sta controllando il timestamp di fine .. Puoi per favore controllare. Fammi sapere cosa succede se ho i registri come 2014-04-07 23:59:58. Voglio dire senza parentesi graffe
Tra il

@Amit, ha aggiornato la risposta
maxschlepzig

Anche se non penso che questo sia un problema di stringa (vedi la mia risposta ), potresti rendere il tuo molto più leggibile, e probabilmente un po 'più veloce, non ripetendo tutti i test: $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

Ciao Max, un altro piccolo dubbio ... Se ho qualcosa come Apr-07-2014 10:51:17. Quindi quali cambiamenti devo fare .. Ho provato 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} codema non funziona
Tra il

@awk_FTW, ha modificato il codice in modo tale che la regex sia esplicitamente condivisa.
maxschlepzig,

12

Dai un'occhiata dategrepa https://github.com/mdom/dategrep

Descrizione:

dategrep cerca nei file di input indicati le righe corrispondenti a un intervallo di date e le stampa su stdout.

Se dategrep funziona su un file ricercabile, può fare una ricerca binaria per trovare la prima e l'ultima riga per stampare in modo abbastanza efficiente. dategrep può anche leggere da stdin se uno degli argomenti del nome file è solo un trattino, ma in questo caso deve analizzare ogni singola riga che sarà più lenta.

Esempi di utilizzo:

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

Sebbene questa limitazione possa renderlo inadatto alla tua domanda esatta:

Al momento dategrep morirà non appena troverà una linea non analizzabile. In una versione futura questo sarà configurabile.


Ho appreso questo comando solo un paio di giorni fa per gentile concessione di onethingwell.org/post/81991115668/dategrep , quindi complimenti a lui!
cpugeniusmv,

3

Un'alternativa a awkuno strumento non standard è usare GNU grepper i suoi greps contestuali. GNU grepti consentirà di specificare il numero di righe dopo una corrispondenza positiva con cui stampare -Ae le righe precedenti con cui stampare -BAd esempio:

[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

Quanto sopra essenzialmente dice grepdi stampare le 10.000 linee che seguono la linea che corrisponde allo schema da cui si desidera iniziare, facendo in modo che il tuo output inizi dove vuoi e vai fino alla fine (si spera) mentre il secondo egrepnel la pipeline dice di stampare solo la linea con il delimitatore finale e le 10.000 linee prima di essa. Il risultato finale di questi due sta iniziando dove vuoi e non passa dove hai detto di fermarti.

10.000 è solo un numero che mi è venuto in mente, sentiti libero di cambiarlo in un milione se pensi che il tuo output sarà troppo lungo.


Come funzionerà se non ci sono voci di registro per gli intervalli di inizio e fine? Se OP vuole tutto tra le 14:00 e le 15:00, ma non ci sono voci di registro per le 14:00, allora?

Parlerà così come quello sedche è anche alla ricerca di corrispondenze letterali. dategrepè probabilmente la risposta più corretta di tutte quelle fornite (dal momento che devi essere in grado di ottenere "fuzzy" su quali timestamp accetterai) ma come dice la risposta, la stavo solo citando come alternativa. Detto questo, se il registro è abbastanza attivo da generare un output sufficiente a giustificare il taglio, probabilmente avrà anche una sorta di voce per il periodo di tempo specificato.
Bratchley,

0

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

Copia questo in un file. Se non si desidera visualizzare le informazioni di debug, il debug viene inviato a stderr, quindi è sufficiente aggiungere "2> / dev / null"


1
Ciò non visualizzerà i file di registro che non dispongono di timestamp.
Tra il

@Amit, sì, ci hai provato?
UnX

@rMistero, non funzionerà perché se non vi è alcuna voce di registro alle 22:30, l'intervallo non verrà terminato. Come menzionato in OP, i tempi di inizio e fine potrebbero non essere nei registri. Puoi modificare il tuo regex affinché funzioni, ma perderai la risoluzione e non sarai mai garantito in anticipo che l'intervallo terminerà al momento giusto.

@awk_FTW questo era un esempio, non ho usato i timestamp forniti da Amit. Anche in questo caso regex può essere utilizzato. Sono d'accordo che non funzionerà se il timestamp non esiste se fornito in modo esplicito o nessuna corrispondenza regex timestamp. Lo migliorerò presto ..
UnX

"Come indicato da OP, i tempi di inizio e fine potrebbero non essere nei registri." No, leggi di nuovo l'OP. OP dice che saranno presenti ma le linee intermedie non inizieranno necessariamente con un timestamp. Non ha nemmeno senso dire che i tempi di arresto potrebbero non essere presenti. Come potresti mai dire a qualsiasi strumento dove fermarsi se il marker di terminazione non è garantito per essere lì? Non ci sarebbero criteri per fornire lo strumento per dire dove interrompere l'elaborazione.
Bratchley,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.