Как извлечь журналы между двумя отметками времени


25

Я хочу извлечь все журналы между двумя отметками времени. Некоторые строки могут не иметь метки времени, но я хочу, чтобы эти строки тоже. Короче говоря, я хочу каждую строку, которая попадает под две отметки времени. Моя структура журнала выглядит так:

[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

Предположим, я хочу извлечь все между 2014-04-07 23:00и 2014-04-08 02:00.

Обратите внимание, что отметка времени начала или окончания времени может отсутствовать в журнале, но я хочу каждую строку между этими двумя отметками времени.


Возможный дубликат stackoverflow.com/questions/7575267/…
Рамеш

Вам нужно сделать это только один раз или программно в разное время?
Братчли

Причина, по которой я спрашиваю, заключается в том, что вы можете сделать два контекстных grep (один, чтобы захватить все после начального разделителя, а другой, чтобы остановить печать в конечном разделителе), если вы знаете литеральные значения. Если даты / время могут измениться, то вы можете легко сгенерировать их на лету, передавая пользовательский ввод через date -dкоманду и используя его для построения шаблона поиска.
Братчли

@ Рамеш, упомянутый вопрос слишком широкий.
maxschlepzig

@JoelDavis: я хочу сделать это программно. Поэтому каждый раз, когда мне просто нужно ввести желаемую метку времени, чтобы извлечь журналы между этими метками времени в моем / TMP месте.
Амит

Ответы:


19

Вы можете использовать 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

Где:

  • -Fопределяет символы [и ]как разделители полей, используя регулярное выражение
  • $0 ссылается на полную строку
  • $2 ссылается на поле даты
  • p используется в качестве логической переменной, которая защищает фактическую печать
  • $0 ~ /regex/ верно, если регулярное выражение соответствует $0
  • >=используется для лексикографического сравнения строк (эквивалентно, например, strcmp())

вариации

Приведенная выше командная строка реализует сопоставление правого интервала времени . Чтобы получить семантику с закрытым интервалом, просто увеличьте правильную дату, например:

$ 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

Если вы хотите сопоставить метки времени в другом формате, вам нужно изменить $0 ~ /^\[/подвыражение. Обратите внимание, что раньше игнорировались строки без каких-либо отметок времени из логики включения / выключения печати.

Например, для формата отметки времени YYYY-MM-DD HH24:MI:SS(без []скобок) вы можете изменить команду следующим образом:

$ 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

(обратите внимание, что также изменен разделитель полей - переход к пустому / непустому, по умолчанию)


Спасибо, что поделились сценарием, но он не проверяет конечную метку времени. Можете ли вы проверить. Также дайте мне знать, что если у меня есть журналы, такие как 2014-04-07 23:59:58. Я имею в виду без фигурных скобок
Амит

@Amit, обновил ответ
maxschlepzig

Хотя я не думаю, что это строковая проблема (см. Мой ответ ), вы могли бы сделать свой текст более читабельным и, возможно, немного быстрее, не повторяя все тесты: $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

Привет Макс, Еще одно маленькое сомнение .. Если у меня есть что-то вроде Апр-07-2014 10:51:17. Тогда какие изменения мне нужно сделать .. Я пытался 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> = "Апр-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> = "Апр-07-2014 12:00:01" {p = 0}, codeно не работает
Amit

@awk_FTW, изменил код таким образом, что регулярное выражение явно используется совместно.
maxschlepzig

12

Проверьте dategrepна https://github.com/mdom/dategrep

Описание:

dategrep ищет в именованных входных файлах строки, соответствующие диапазону дат, и выводит их на стандартный вывод.

Если dategrep работает с доступным для поиска файлом, он может выполнить бинарный поиск, чтобы найти первую и последнюю строку для печати достаточно эффективно. dategrep также может читать из stdin, если один из аргументов имени файла является просто дефисом, но в этом случае он должен анализировать каждую строку, которая будет медленнее.

Примеры использования:

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

Хотя это ограничение может сделать это неподходящим для вашего точного вопроса:

В данный момент dategrep умрет, как только найдет строку, которую нельзя разобрать. В будущей версии это будет настраиваться.


Я узнал об этой команде всего пару дней назад благодаря onethingwell.org/post/81991115668/dategrep так, слава ему!
cpugeniusmv

3

Одной из альтернатив awkили нестандартных инструментов является использование GNU grepдля контекстных команд. GNU grepпозволяет указать количество строк после положительного совпадения для печати -Aи предыдущие строки для печати -BНапример:

[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

Вышесказанное в сущности говорит grepнапечатать 10000 строк, которые следуют за линией, соответствующей шаблону, с которого вы хотите начать, эффективно заставляя ваш вывод начинаться с того места, куда вы хотите, и идти до конца (надеюсь), тогда как вторая egrepв pipe указывает ему печатать только строку с конечным разделителем и 10 000 строк перед ним. Конечный результат этих двух начинается с того места, где вы хотите, и не проходит, где вы сказали, чтобы он остановился.

10000 - это просто число, которое я придумал, смело меняйте его на миллион, если вы думаете, что ваш результат будет слишком длинным.


Как это будет работать, если нет записи в журнале для начального и конечного диапазонов? Если OP хочет все с 14:00 до 15:00, но нет записи в журнале для 14:00, тогда?

Он будет рассказывать о том, sedчто также ищет буквальные совпадения. dategrepэто, пожалуй, самый правильный ответ из всех приведенных (поскольку вам нужно быть «неясным» в отношении того, какие временные метки вы будете принимать), но, как говорится в ответе, я просто упомянул его в качестве альтернативы. Тем не менее, если журнал достаточно активен, чтобы генерировать достаточно выходных данных, чтобы гарантировать сокращение, он, вероятно, также будет иметь какую- то запись для данного периода времени.
Братчли

0

Используя 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"

Скопируйте это в файл. Если вы не хотите видеть информацию об отладке, отладка отправляется в stderr, поэтому просто добавьте «2> / dev / null»


1
Это не будет отображать файлы журнала, которые не имеют метки времени.
Амит

@ Да, да, попробовал?
UnX

@rMistero, он не будет работать, потому что если в 22:30 нет записи в журнале, диапазон не будет прекращен. Как упоминалось в OP, время начала и окончания может не совпадать с журналами. Вы можете настроить свое регулярное выражение, чтобы оно работало, но вы потеряете разрешение и никогда не будете заранее гарантированы, что диапазон будет прекращен в нужное время.

@awk_FTW это был пример, я не использовал временные метки, предоставленные Амитом. Снова можно использовать регулярные выражения. Я согласен с тем, что он не будет работать, если отметка времени не существует, если она предоставлена ​​явно, или нет совпадений с регулярным выражением отметки времени. Я скоро
улучшу

«Как упоминалось в OP, время начала и окончания может не совпадать с журналами». Нет, прочитайте ОП еще раз. ОП говорит, что они БУДУТ присутствовать, но промежуточные строки не обязательно начинаются с отметки времени. Даже не имеет смысла говорить, что время остановки может отсутствовать. Как вы могли когда-либо сказать любому инструменту, где остановиться, если маркер завершения не гарантированно будет там? Не было бы никаких критериев, чтобы дать инструменту, чтобы сказать ему, где остановить обработку.
Братчли
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.