Cara mengekstrak log di antara dua prangko waktu

25

Saya ingin mengekstrak semua log di antara dua cap waktu. Beberapa baris mungkin tidak memiliki timestamp, tapi saya juga ingin garis itu. Singkatnya, saya ingin setiap baris yang berada di bawah dua prangko waktu. Struktur log saya terlihat seperti:

[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

Misalkan saya ingin mengekstraksi semuanya antara 2014-04-07 23:00dan 2014-04-08 02:00.

Harap perhatikan stempel waktu mulai atau stempel waktu akhir mungkin tidak ada di log, tapi saya ingin setiap baris di antara kedua stempel waktu ini.

Amit
sumber
Kemungkinan duplikat stackoverflow.com/questions/7575267/…
Ramesh
Apakah Anda hanya perlu melakukan ini sekali saja atau secara terprogram pada berbagai waktu?
Bratchley
Alasan yang saya tanyakan adalah karena Anda dapat melakukan dua grep kontekstual (satu untuk mengambil semuanya setelah pembatas mulai dan yang lain untuk berhenti mencetak pada pembatas akhir) jika Anda tahu nilai literalnya. Jika tanggal / waktu dapat berubah, tou dapat dengan mudah membuat ini dengan cepat dengan memasukkan input pengguna melalui date -dperintah dan menggunakannya untuk membangun pola pencarian.
Bratchley
@ Ramesh, pertanyaan yang dirujuk terlalu luas.
maxschlepzig
@ JoelDavis: Saya ingin melakukannya secara terprogram. Jadi setiap kali saya hanya perlu memasukkan cap waktu yang diinginkan untuk mengekstrak log antara cap waktu itu di lokasi / tmp saya.
Amit

Jawaban:

19

Anda dapat menggunakan awkini:

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

Dimana:

  • -Fmenentukan karakter [dan ]sebagai pemisah bidang menggunakan ekspresi reguler
  • $0 referensi satu baris lengkap
  • $2 referensi bidang tanggal
  • p digunakan sebagai variabel boolean yang menjaga pencetakan aktual
  • $0 ~ /regex/ benar jika regex cocok $0
  • >=digunakan untuk membandingkan string secara leksikografis (setara dengan misalnya strcmp())

Variasi

Baris perintah di atas mengimplementasikan pencocokan interval waktu buka-kanan . Untuk mendapatkan semantik interval tertutup cukup tambahkan tanggal kanan Anda, misalnya:

$ 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

Jika Anda ingin mencocokkan cap waktu dalam format lain, Anda harus memodifikasi $0 ~ /^\[/sub-ekspresi. Perhatikan bahwa ini digunakan untuk mengabaikan garis tanpa cap waktu dari logika on / off cetak.

Misalnya untuk format cap waktu seperti YYYY-MM-DD HH24:MI:SS(tanpa []kawat gigi) Anda dapat memodifikasi perintah seperti ini:

$ 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

(perhatikan bahwa pemisah bidang juga diubah - ke transisi kosong / tidak kosong, default)

maxschlepzig
sumber
Thanx untuk berbagi skrip tetapi tidak memeriksa stempel waktu akhir .. Bisakah Anda periksa. Juga beri tahu saya bagaimana jika saya memiliki log seperti 2014-04-07 23:59:58. Maksud saya tanpa kawat gigi
Amit
@Amit, perbarui jawabannya
maxschlepzig
Meskipun saya tidak berpikir ini adalah masalah string (lihat jawaban saya ), Anda dapat membuat Anda lebih mudah dibaca, dan mungkin sedikit lebih cepat, dengan tidak mengulangi semua tes: $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
Hai Max, Satu lagi keraguan kecil .. Jika saya punya sesuatu seperti Apr-07-2014 10:51:17. Lalu perubahan apa yang perlu saya lakukan .. Saya mencoba 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} codetetapi tidak berfungsi
Amit
@awk_FTW, ubah kode sehingga regex dibagikan secara eksplisit.
maxschlepzig
12

Lihat dategrepdi https://github.com/mdom/dategrep

Deskripsi:

dategrep mencari file input bernama untuk baris yang cocok dengan rentang tanggal dan mencetaknya ke stdout.

Jika dategrep bekerja pada file yang dapat dicari, ia dapat melakukan pencarian biner untuk menemukan baris pertama dan terakhir untuk mencetak dengan cukup efisien. dategrep juga dapat membaca dari stdin jika salah satu argumen nama file hanya tanda hubung, tetapi dalam kasus ini harus mengurai setiap baris yang akan lebih lambat.

Contoh penggunaan:

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

Meskipun batasan ini mungkin membuat ini tidak sesuai untuk pertanyaan persis Anda:

Saat ini dategrep akan mati segera setelah menemukan garis yang tidak dapat diuraikan. Dalam versi masa depan ini akan dapat dikonfigurasi.

cpugeniusmv
sumber
Saya belajar tentang perintah ini hanya beberapa hari yang lalu milik onethingwell.org/post/81991115668/dategrep begitu, terima kasih kepadanya!
cpugeniusmv
3

Salah satu alternatif awkatau alat non-standar adalah dengan menggunakan GNU grepuntuk greps kontekstualnya. GNU grepakan membiarkan Anda menentukan jumlah garis setelah kecocokan positif untuk dicetak dengan -Adan garis sebelumnya untuk mencetak dengan -BMisalnya:

[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

Di atas pada dasarnya memberitahu grepuntuk mencetak 10.000 baris yang mengikuti garis yang cocok dengan pola yang ingin Anda mulai, secara efektif membuat output Anda mulai di mana Anda ingin dan pergi sampai akhir (mudah-mudahan) sedangkan yang kedua egrepdi pipeline memberitahukannya untuk hanya mencetak garis dengan pembatas akhir dan 10.000 baris sebelumnya. Hasil akhir dari keduanya adalah mulai di mana Anda inginkan dan tidak akan berlalu di mana Anda menyuruhnya berhenti.

10.000 hanya angka yang saya buat, jangan ragu untuk mengubahnya menjadi satu juta jika Anda berpikir output Anda akan terlalu lama.

Bratchley
sumber
Bagaimana ini akan bekerja jika tidak ada entri log untuk rentang awal dan akhir? Jika OP menginginkan semuanya antara 14:00 dan 15:00, tetapi tidak ada entri log untuk 14:00, lalu?
Ini akan mengatakan tentang serta sedyang juga mencari kecocokan literal. dategrepmungkin jawaban yang paling benar dari semua yang diberikan (karena Anda harus bisa mendapatkan "fuzzy" pada cap waktu apa yang akan Anda terima) tetapi seperti jawabannya, saya hanya menyebutkannya sebagai alternatif. Yang mengatakan, jika log cukup aktif untuk menghasilkan cukup output ke surat perintah pemotongan itu mungkin juga akan memiliki beberapa jenis entri untuk timeperiod diberikan.
Bratchley
0

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

Salin ini dalam file. Jika Anda tidak ingin melihat info debug, debugging dikirim ke stderr jadi tambahkan saja "2> / dev / null"

UnX
sumber
1
Ini tidak akan menampilkan file log yang tidak memiliki cap waktu.
Amit
@Amit, ya itu akan, sudahkah Anda mencoba?
UnX
@rMistero, itu tidak akan berfungsi karena jika tidak ada entri log pada pukul 22:30, kisaran tidak akan diakhiri. Seperti yang disebutkan OP, waktu mulai dan berhenti mungkin tidak ada dalam log. Anda dapat mengubah regex agar berfungsi, tetapi Anda akan kehilangan resolusi dan tidak pernah dijamin sebelumnya bahwa rentang akan berakhir pada waktu yang tepat.
@awk_FTW ini adalah contoh, saya tidak menggunakan cap waktu yang disediakan oleh Amit. Sekali lagi regex bisa digunakan. Saya setuju bahwa itu tidak akan berfungsi jika cap waktu tidak ada saat disediakan secara eksplisit atau tidak ada timestamp yang cocok dengan regex. Saya akan segera memperbaikinya ..
UnX
"Seperti yang disebutkan OP, waktu mulai dan berhenti mungkin tidak ada dalam log." Tidak, baca OP lagi. OP mengatakan bahwa mereka AKAN hadir tetapi garis intervensi tidak harus dimulai dengan cap waktu. Bahkan tidak masuk akal untuk mengatakan waktu berhenti mungkin tidak ada. Bagaimana Anda bisa memberi tahu alat apa pun tempat berhenti jika penanda penghentian tidak dijamin ada di sana? Tidak akan ada kriteria untuk memberikan alat untuk memberi tahu di mana harus berhenti memproses.
Bratchley