Cómo extraer registros entre dos marcas de tiempo

25

Quiero extraer todos los registros entre dos marcas de tiempo. Es posible que algunas líneas no tengan la marca de tiempo, pero también quiero esas líneas. En resumen, quiero cada línea que se clasifique en dos marcas de tiempo. Mi estructura de registro se ve así:

[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

Supongamos que quiero extraer todo entre 2014-04-07 23:00y 2014-04-08 02:00.

Tenga en cuenta que la marca de hora de inicio o la marca de hora de finalización pueden no estar allí en el registro, pero quiero que cada línea entre estas dos marcas de tiempo.

Amit
fuente
Posible duplicado de stackoverflow.com/questions/7575267/…
Ramesh
¿Solo necesita hacer esto solo una vez o mediante programación en varias ocasiones?
Bratchley
La razón por la que pregunto es porque puede hacer dos grep contextuales (uno para tomar todo después del delimitador inicial y otro para detener la impresión en el delimitador final) si conoce los valores literales. Si las fechas / horas pueden cambiar, puede generarlas fácilmente sobre la marcha alimentando la entrada del usuario a través del date -dcomando y usándola para construir el patrón de búsqueda.
Bratchley
@Ramesh, la pregunta referenciada es demasiado amplia.
maxschlepzig
@JoelDavis: quiero hacerlo mediante programación. Entonces, cada vez que necesito ingresar la marca de tiempo deseada para extraer los registros entre esas marcas de tiempo en mi ubicación / tmp.
Amit

Respuestas:

19

Puedes usar awkpara esto:

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

Dónde:

  • -Fespecifica los caracteres [y ]como separadores de campo utilizando una expresión regular
  • $0 hace referencia a una línea completa
  • $2 hace referencia al campo de fecha
  • p se utiliza como variable booleana que protege la impresión real
  • $0 ~ /regex/ es cierto si la expresión regular coincide $0
  • >=se usa para comparar cadenas lexicográficamente (equivalente a, por ejemplo strcmp())

Variaciones

La línea de comando anterior implementa la coincidencia de intervalo de tiempo de apertura a la derecha . Para obtener una semántica de intervalo cerrado, simplemente incremente su fecha correcta, por ejemplo:

$ 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

En caso de que desee hacer coincidir las marcas de tiempo en otro formato, debe modificar la $0 ~ /^\[/expresión secundaria. Tenga en cuenta que solía ignorar líneas sin ninguna marca de tiempo de la lógica de encendido / apagado de impresión.

Por ejemplo, para un formato de marca de tiempo como YYYY-MM-DD HH24:MI:SS(sin []llaves), puede modificar el comando de esta manera:

$ 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

(tenga en cuenta que también se cambia el separador de campo - a transición en blanco / no en blanco, el valor predeterminado)

maxschlepzig
fuente
Gracias por compartir la secuencia de comandos pero no verifica la marca de tiempo de finalización. ¿Pueden verificar? También hágame saber qué pasa si tengo los registros como 2014-04-07 23:59:58. Quiero decir sin llaves
Amit
@Amit, actualizó la respuesta
maxschlepzig
Aunque no creo que este sea un problema de cadena (vea mi respuesta ), podría hacer que el suyo sea mucho más legible, y probablemente un poco más rápido, al no repetir todas las pruebas: $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
Hola Max, una pequeña duda más ... Si tengo algo como Abr-07-2014 10:51:17. Entonces, ¿qué cambios debo hacer? Intenté 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> = "Abr-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> = "Abr-07-2014 12:00:01" {p = 0} codepero no funciona
Amit 10/14
@awk_FTW, cambió el código de modo que la expresión regular se comparte explícitamente.
maxschlepzig
12

Echa un vistazo dategrepen https://github.com/mdom/dategrep

Descripción:

dategrep busca en los archivos de entrada con nombre líneas que coincidan con un intervalo de fechas y las imprime en stdout.

Si dategrep funciona en un archivo buscable, puede hacer una búsqueda binaria para encontrar la primera y la última línea para imprimir de manera bastante eficiente. dategrep también puede leer desde stdin si uno de los argumentos del nombre de archivo es solo un guión, pero en este caso tiene que analizar cada línea que será más lenta.

Ejemplos de uso:

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

Aunque esta limitación puede hacer que esto no sea adecuado para su pregunta exacta:

En este momento, dategrep morirá tan pronto como encuentre una línea que no se pueda analizar. En una versión futura esto será configurable.

cpugeniusmv
fuente
Hace un par de días me enteré de este comando por cortesía de onethingwell.org/post/81991115668/dategrep , así que felicitaciones a él.
cpugeniusmv
3

Una alternativa awko una herramienta no estándar es usar GNU greppara sus greps contextuales. GNU greple permitirá especificar el número de líneas después de una coincidencia positiva para imprimir -Ay las líneas anteriores para imprimir. -BPor ejemplo:

[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

Lo anterior básicamente le dice grepque imprima las 10,000 líneas que siguen a la línea que coincide con el patrón en el que desea comenzar, haciendo que su salida comience efectivamente donde lo desea e ir hasta el final (con suerte) mientras que el segundo egrepen el pipeline le dice que solo imprima la línea con el delimitador final y las 10,000 líneas anteriores. El resultado final de estos dos es comenzar donde quieres y no pasar donde le dijiste que se detuviera.

10,000 es solo un número que se me ocurrió, no dude en cambiarlo a un millón si cree que su producción será demasiado larga.

Bratchley
fuente
¿Cómo funcionará esto si no hay una entrada de registro para los rangos inicial y final? Si OP quiere todo entre las 14:00 y las 15:00, pero no hay entrada de registro para las 14:00, ¿entonces?
Será palabra casi tan bien como el sedque también está en busca de coincidencias literales. dategrepes probablemente la respuesta más correcta de todas las que se dan (ya que necesita poder "confundirse" con las marcas de tiempo que aceptará), pero como dice la respuesta, solo lo mencioné como una alternativa. Dicho esto, si el registro está lo suficientemente activo como para generar suficiente salida para garantizar el corte, probablemente también tenga algún tipo de entrada para el período de tiempo dado.
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 esto en un archivo. Si no desea ver información de depuración, la depuración se envía a stderr, así que simplemente agregue "2> / dev / null"

UnX
fuente
1
Esto no mostrará los archivos de registro que no tienen marca de tiempo.
Amit
@Amit, sí, ¿lo has intentado?
UnX
@rMistero, no funcionará porque si no hay una entrada de registro a las 22:30, el rango no terminará. Como se mencionó en OP, las horas de inicio y finalización pueden no estar en los registros. Puede ajustar su expresión regular para que funcione, pero perderá resolución y nunca se le garantizará de antemano que el rango terminará en el momento adecuado.
@awk_FTW este fue un ejemplo, no utilicé las marcas de tiempo proporcionadas por Amit. De nuevo se puede usar regex. Estoy de acuerdo en que no funcionará si la marca de tiempo no existe cuando se proporciona explícitamente o no hay coincidencias de expresiones regulares de marca de tiempo. Lo
mejoraré
"Como OP mencionó, las horas de inicio y finalización pueden no estar en los registros". No, lea el OP nuevamente. OP dice que ESTARÁN presentes, pero las líneas intermedias no necesariamente comenzarán con una marca de tiempo. Ni siquiera tiene sentido decir que los tiempos de parada podrían no estar presentes. ¿Cómo podría decirle a alguna herramienta dónde detenerse si no se garantiza que el marcador de terminación esté allí? No habría criterios para darle a la herramienta que le diga dónde detener el procesamiento.
Bratchley