2つのタイムスタンプ間でログを抽出する方法


25

2つのタイムスタンプ間のすべてのログを抽出したい。一部の行にはタイムスタンプがない場合がありますが、それらの行も必要です。つまり、2つのタイムスタンプの下にあるすべての行が必要です。私のログ構造は次のようになります。

[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ます。

開始タイムスタンプまたは終了タイムスタンプがログにない場合がありますが、これら2つのタイムスタンプの間のすべての行が必要です。



これを1回だけ行う必要がありますか、それともさまざまな時間にプログラムで行う必要がありますか?
ブラッチリー14

私が尋ねる理由は、リテラル値がわかっている場合、2つのコンテキストgrepを実行できるためです(1つは開始区切り文字の後にすべてを取得し、もう1つは終了区切り文字で印刷を停止します)。日付/時刻が変更される可能性がある場合、touはdate -dコマンドを介してユーザー入力を供給し、それを使用して検索パターンを構築することにより、それらをその場で簡単に生成できます。
ブラッチリー14

@Ramesh、参照されている質問は広すぎます。
maxschlepzig 14

@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/ 正規表現が一致する場合はtrue $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 14

私は、これは文字列の問題だと思います(表示されていないが、私の答えを:)、あなたはあなたがはるかに読みやすくでき、そしておそらく少し速く、すべてのテストを繰り返していないことで $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

こんにちはマックス、もう1つ小さな疑問.. 2014年4月7日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> = "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} codeしかし機能しません
アミット

@awk_FTW、正規表現が明示的に共有されるようにコードを変更しました。
maxschlepzig 14

12

https://github.com/mdom/dategrepでチェックしdategrepください

説明:

dategrepは、指定された入力ファイルで日付範囲に一致する行を検索し、stdoutに出力します。

dategrepがシーク可能なファイルで機能する場合、バイナリ検索を実行して最初と最後の行を見つけ、かなり効率的に印刷できます。dategrepは、ファイル名の引数の1つがハイフンの場合、stdinから読み取ることもできますが、この場合、1行ごとに解析する必要があり、速度が低下します。

使用例:

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 14

3

代替awkツールまたは非標準ツールの1つgrepは、コンテキストグレープにGNUを使用することです。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行のみを印刷するように指示します。これら2つの結果は、目的の場所から開始し、停止するように指示した場所を通過しません。

10,000は単なる数字であり、出力が長すぎると思われる場合は、気軽に100万に変更してください。


開始範囲と終了範囲のログエントリがない場合、これはどのように機能しますか?OPが14:00〜15:00の間にすべてを必要としているが、14:00のログエントリがない場合、

これはsed、文字通りの一致を検索しているだけでなく、についても説明します。dategrepおそらく、与えられたすべてのタイムスタンプの中で最も正しい答えです(受け入れるタイムスタンプで「ファジー」を取得できるようにする必要があるため)。とはいえ、ログが十分にアクティブであり、切断を保証するのに十分な出力を生成する場合、おそらく特定の期間に何らかの種類のエントリが存在することにもなります。
ブラッチリー14

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
これは、タイムスタンプのないログファイルを表示しません。
アミット14

@Amit、はい、あなたは試しましたか?
UnX 14

@ rMistero、22:30にログエントリがない場合、範囲は終了しないため、機能しません。OPが述べたように、開始時刻と停止時刻がログにない場合があります。正規表現を調整して機能させることはできますが、解像度が失わ、範囲が適切なタイミングで終了することが事前に保証されることありません。

@awk_FTWこれは例であり、Amitが提供するタイムスタンプは使用しませんでした。再び正規表現を使用できます。明示的に指定されたタイムスタンプが存在しない場合、またはタイムスタンプの正規表現が一致しない場合は機能しないと考えることに同意します。すぐに改善します。
UnX14年

「OPが述べたように、開始時刻と終了時刻がログにない場合があります。」いいえ、もう一度OPを読んでください。OPはそれらは存在すると言いますが、間にある行は必ずしもタイムスタンプで始まるとは限りません。停止時間が存在しない可能性があると言うことすら意味がありません。終了マーカーがそこにあることが保証されていない場合、どのように停止するかをツールに指示できますか?処理を停止する場所をツールに伝えるための基準はありません。
ブラッチリー14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.