Як витягнути журнали між двома часовими марками


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/…
Рамеш

Вам потрібно це робити один раз або програмно в різний час?
Братчлі

Причина, яку я прошу, полягає в тому, що ви можете зробити два контекстних грепа (один, щоб схопити все після стартового роздільника, а інший - зупинити друк на кінцевому роздільнику), якщо ви знаєте буквальні значення. Якщо дати / часи можуть змінюватися, tou може легко генерувати їх на льоту, подаючи введення користувача за допомогою date -dкоманди та використовуючи це для побудови шаблону пошуку.
Братчлі

@Ramesh, посилається на це питання занадто широко.
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.04.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}, codeале його не працює
Amit

@awk_FTW змінив код таким чином, що регулярно виражається загальний вираз.
maxschlepzig

12

Перевірте за dategrepадресою https://github.com/mdom/dategrep

Опис:

dategrep здійснює пошук у названих вхідних файлах для рядків, що відповідають діапазону дат, і виводить їх у stdout.

Якщо датагреп працює на файлі, який можна шукати, він може виконати двійковий пошук, щоб знайти перший і останній рядок, щоб надрукувати досить ефективно. 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" -

Хоча це обмеження може зробити це непридатним для вашого точного запитання:

На даний момент зріп дати помре, як тільки він знайде лінію, яка не проходить. У майбутній версії це можна настроїти.


Про цю команду я дізнався лише пару днів тому, з люб’язності 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про друк 10 000 рядків, які відповідають лінії, що відповідає шаблону, з якого ви хочете почати, фактично змушуючи свій вихід починати там, де ви цього хочете, і продовжувати до кінця (сподіваємось), тоді як другий egrepу конвеєр повідомляє, що він повинен надрукувати лише рядок із роздільником і закінченням 10 000 рядків. Кінцевий результат цих двох - починається там, де ви хочете, і не проходите там, де ви сказали йому зупинитися.

10 000 - це лише число, яке я придумав, сміливо міняйте його на мільйон, якщо ви думаєте, що ваш результат буде занадто довгим.


Як це буде працювати, якщо для початкового та кінцевого діапазонів немає запису журналу? Якщо ОП хоче все між 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 не буде запису журналу, діапазон не буде припинено. Як зазначалося в ОП, час запуску і зупинки може бути не в журналах. Ви можете налаштувати свій регекс, щоб він працював, але ви втратите дозвіл і ніколи не будете гарантовані заздалегідь, що діапазон припиниться в потрібний час.

@awk_FTW це був приклад, я не використовував часові позначки, передбачені Amit. Знову можна використовувати регулярний гекс. Я погоджуюся, що він не працюватиме, якщо часова марка не існує, якщо явно надається або немає зразків часових позначок. Я скоро її вдосконалю ..
UnX

"Як згадувалося в ОП, час запуску і зупинки може бути не в журналах." Ні, ще раз прочитайте ОП. ОП заявляє, що вони будуть присутні, але втручаються лінії необов'язково починатись із мітки часу. Навіть не має сенсу стверджувати, що часу зупинки може бути відсутнім. Як ви могли коли-небудь сказати будь-якому інструменту, де зупинитися, якщо маркер припинення не гарантовано буде там? Не було б ніяких критеріїв, щоб дати інструменту сказати, де зупинити обробку.
Братчлі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.