Извлеките журналы, зарегистрированные в пределах двух временных меток

Я нахожусь в ситуации, когда написал сценарий для извлечения строк из файла журнала в течение указанного периода времени. Этот сценарий работает нормально, пока я не обнаружил, что он печатает только те строки, которые имеют временную метку, зарегистрированную в течение указанного времени, и оставляет строки, которые не имеют временной метки, но зарегистрированы в течение указанного периода времени. Те строки, которые не имеют временной метки, но присутствуют в указанном временном интервале, также должны быть напечатаны. Но я не знаю, как этого добиться.

Ниже приведен файл журнала

[17:02:12:161][01-03-2024]some log info here:
step1
step2
step3
[17:02:12:163][01-03-2024]some log here
a
b
c
[17:02:12:185][01-03-2024]Time taken   : 11

временная метка начала: [17:02:12:161][01-03-2024] временная метка окончания: [17:02:12:163][01-03-2024]

Но мне также нужны эти строки a, b, c, поскольку они также регистрируются во время конечной отметки времени.

Обратите внимание, что у меня нет полномочий изменять этот формат временной метки. Я знаю, что это не правильный формат.

Ниже приведен сценарий

#!/bin/bash

# Function to check the timestamp format
timestamp_pattern_checker() {
    local input_pattern = "^\[([0-9]{2}:[0-9]{2}:[0-9]{2}:[0-9]{3})\]\[([0-9]{2}-[0-9]{2}-[0-9]{4})\]$"
    if [[ ! $1 =~ $input_pattern ]]; then
        echo "Invalid Timestamp pattern"
        echo "Timestamps should be in the format '[HH:MM:SS:SSS][DD-MM-YYYY]' or HH"
        exit 1
    fi
}

# Function to convert hours to timestamp format
convert_hours_to_timestamp() {
    local hour=$1
    printf "[%02d:00:00:000]" "$hour"
}

# Check if correct number of arguments are passed
if [ "$#" -ne 3 ]; then
    echo "Usage: $0 <log_file_name> <start_timestamp> <end_timestamp>"
    echo "Timestamps should be in the format '[HH:MM:SS:SSS][DD-MM-YYYY]' or as integers representing hours (00 to 23)"
    exit 1
fi

log_file=$1
start_input=$2
end_input=$3

# Check if log file exists
if [ ! -f "$log_file" ]; then
    echo "File not found: $log_file"
    exit 1
fi

# Determine if inputs are hours or full timestamps
if [[ $start_input =~ ^[0-9]{2}$ && $end_input =~ ^[0-9]{2}$ ]]; then
    if [[ $start_input -ge 0 && start_input -le 23 && $end_input -ge 0 && $end_input -le 23 ]]; then
        if [[ $start_input -le $end_input ]]; then
            start_timestamp=$(convert_hours_to_timestamp "$start_input")
            end_timestamp=$(convert_hours_to_timestamp "$end_input")

            # Extract unique dates from log file
            unique_dates=$(awk -F'[][]' '{print $4}' "$log_file" | sort | uniq)

            start_timestamps=()
            end_timestamps=()
            for date in $unique_dates; do
                start_timestamps+=("${start_timestamp}[$date]")
                end_timestamps+=("${end_timestamp}[$date]")
            done
        else
           echo "Error: start hour must be less than or equal to end hour."
           exit 1
        fi
    else
       echo "Error: Hours must be between 00 and 23."
       exit 1
    fi
else
    start_timestamp=$start_input
    end_timestamp=$end_input
    timestamp_pattern_checker "$start_timestamp"
    timestamp_pattern_checker "$end_timestamp"
    start_timestamps=("$start_timestamp")
    end_timestamps=("$end_timestamp")
fi

awk -v starts = "${start_timestamps[*]}" -v ends = "${end_timestamps[*]}" '
  function parsedate(date) {
        split(date, a, /[]:[-]+/)
        return a[6] "-" a[7] "-" a[8] "T" a[1] ":" a[2] ":" a[3] ":" a[4] "." a[5]
  }
  BEGIN {
    split(starts, start_arr, " ")
    split(ends, end_arr, " ")
    for (i in start_arr) {
        st[i] = parsedate(start_arr[i])
        et[i] = parsedate(end_arr[i])
    }
    log_count = 0
  }
  {
    p = parsedate($0)
    for (i in st) {
        if (p >= st[i] && p <= et[i]) {
            print $0
            log_count = 1
        }
    }
  }
  END {
    if (log_count == 0) {
        print "Nothing was logged at this given time frame"
    }
  }' "$log_file"

🤔 А знаете ли вы, что...
Bash поддерживает механизмы подстановки и раскрытия переменных для удобной работы с текстом.


121
5

Ответы:

Попробуйте СЭД:

sed -n "/pattern1/,/pattern2/p" file_name

Этот sed предоставит вам все строки между шаблоном1 и шаблоном2, включая строки шаблона.


Вы можете изменить свой сценарий, чтобы он продолжал печатать до тех пор, пока вы не увидите другую метку времени.

awk -v starts = "${start_timestamps[*]}" -v ends = "${end_timestamps[*]}" '
  function parsedate(date) {
        split(date, a, /[]:[-]+/)
        return a[8] "-" a[7] "-" a[6] "T" a[1] ":" a[2] ":" a[3] ":" a[4] "." a[5]
  }
  BEGIN {
    split(starts, start_arr, " ")
    split(ends, end_arr, " ")
    for (i in start_arr) {
        st[i] = parsedate(start_arr[i])
        et[i] = parsedate(end_arr[i])
    }
    log_count = 0
  }
  /^\[[0-9]{2}:[0-9]{2}:[0-9]{2}:[0-9]{3}\]\[[0-3][0-9]-[01][0-9]-[0-9]{4}\]/{
    p = parsedate($0)
    printing = 0
    for (i in st) {
        if (p >= st[i] && p <= et[i]) {
            printing = 1
            log_count = 1
            break
        }
    }
  }
  printing
  END {
    if (log_count == 0) {
        print "Nothing was logged at this given time frame"
    }
  }' "$log_file"

Это добавляет условие для анализа дат только в строках, которые начинаются с отметки даты, и заменяет print на printing = 1; и тогда условие printing просто напечатает текущую строку, если printing не равно 0. Таким образом, printing будет пересчитываться каждый раз, когда вы видите отметку времени, но в противном случае результат самой последней строки, которая имела отметку времени будет применяться.

Мне также пришлось поменять порядок a[8] и a[6] в parsedate — я думаю, они остались от ошибочной предыдущей версии моего ответа на ваш предыдущий вопрос.

Вот демо-версия с некоторыми отпечатками отладки, которая поможет вам увидеть, что он делает, и, надеюсь, поможет вам разобраться, как самостоятельно устранять неполадки в коде: https://ideone.com/MpA28H


Не пытайтесь использовать для этого sed, иначе это потерпит неудачу, если конкретная временная метка, которую вы хотите использовать в диапазоне, не существует во входных данных. Вот начало, по крайней мере, с любым POSIX awk:

$ cat tst.sh
#!/usr/bin/env bash

awk -v beg='[17:02:12:161][01-03-2024]' -v end='[17:02:12:163][01-03-2024]' '
    BEGIN {
        beg = ts2iso(beg)
        end = ts2iso(end)
    }

    match($0,/^\[([0-9]{2}:){3}[0-9]{3}]\[([0-9]{2}-){2}[0-9]{4}]/) {
        cur = ts2iso(substr($0,1,RLENGTH))
    }

    (beg <= cur) && (cur <= end)

    function ts2iso(old     ,t) {
        # Convert unsortable timestamp to ISO 8601 format
        split(old,t,/[][-]+/)
        return t[5] "-" t[4] "-" t[3] "T" t[2]
    }
' "${@:--}"

$ ./tst.sh file
[17:02:12:161][01-03-2024]some log info here:
step1
step2
step3
[17:02:12:163][01-03-2024]some log here
a
b
c

Я бы использовал GNU AWK для этой задачи следующим образом: пусть file.txt контент будет

[17:02:12:161][01-03-2024]some log info here:
step1
step2
step3
[17:02:12:163][01-03-2024]some log here
a
b
c
[17:02:12:185][01-03-2024]Time taken   : 11

затем

awk -v start = "[17:02:12:161][01-03-2024]" -v end = "[17:02:12:163][01-03-2024]" '
    function parsestamp(stamp){
        patsplit(stamp,a,"[0-9]+");
        return sprintf("%04d%02d%02d%02d%02d%02d%d", a[7], a[6], a[5], a[1], a[2], a[3], a[4])+0
    }
    BEGIN{
        PROCINFO["sorted_in"] = "@ind_num_asc";
        startime=parsestamp(start);
        endtime=parsestamp(end)
    }
    /^\[[0-9:]+\]/{
        inx+=1
    }
    {
        arr[inx]=arr[inx]"\n" $0
    }
    END{
        for(i in arr){
            stamp=parsestamp(arr[i]);
            if (startime<=stamp&&stamp<=endtime){
                print substr(arr[i],2)
}}}' file.txt

дает результат

[17:02:12:161][01-03-2024]some log info here:
step1
step2
step3
[17:02:12:163][01-03-2024]some log here
a
b
c

Объяснение: я определяю функцию parsestamp, которая преобразует временные метки вашего формата в целые числа, которые можно сравнивать (более поздняя временная метка дает большее число). Обратите внимание, что указанная функция создана таким образом, чтобы работать, если после метки времени имеется информация, не связанная с датой и временем. В BEGIN я сообщаю GNU AWKобходить массив по индексу в порядке возрастания и вычислять числовые значения для начала и конца. Если строка начинается с [ одной или нескольких (цифр или :) ], то я предполагаю, что это временная метка, и увеличиваю inx на 1. Я добавляю новую строку и текущую строку к значению массива arr под ключом inx. После того, как все строки обработаны, я перебираю массив arr и для каждого значения вычисляю число на основе содержащейся в нем метки времени. Если он находится внутри диапазона начала-конца (включительно), я печатаю строку без первого символа, то есть новой строки (что здесь связано с тем, как собираются строки).

(Протестировано в GNU Awk 5.1.0)


Решено

Наконец, я понял это с помощью ребят, которые пытались. Я многому научился. Большое спасибо, что обратились ко мне, чтобы помочь. Идеальный ответ, который работает для меня, здесь.

#!/bin/bash

# Function to check the timestamp format
timestamp_pattern_checker() {
    local input_pattern = "^\[([0-9]{2}:[0-9]{2}:[0-9]{2}:[0-9]{3})\]\[([0-9]{2}-[0-9]{2}-[0-9]{4})\]$"
    if [[ ! $1 =~ $input_pattern ]]; then
        echo "Invalid Timestamp pattern"
        echo "Timestamps should be in the format '[HH:MM:SS:SSS][DD-MM-YYYY]' or HH"
        exit 1
    fi
}

# Function to convert hours to timestamp format
# Input: hour (integer)
convert_hours_to_timestamp() {
    local hour=$1
    printf "[%02d:00:00:000]" "$hour"
}

# Function to convert date to timestamp format [DD-MM-YYYY]
# Input: date (string)
convert_date_to_timestamp() {
    local date=$1
    printf "[%s]" "$date"
}

# Main script starts here
if [ "$#" -ne 1 ]; then
    echo "Usage: $0 <log_file>"
    exit 1
fi

log_file=$1

# Check if log file exists
if [ ! -f "$log_file" ]; then
    echo "File not found: $log_file"
    exit 1
fi

read -p "Enter start timestamp (HH or [HH:MM:SS:SSS][DD-MM-YYYY]): " start_input
read -p "Enter end timestamp (HH or [HH:MM:SS:SSS][DD-MM-YYYY]): " end_input

# Determine if inputs are hours or full timestamps
if [[ $start_input =~ ^[0-9]{2}$ && $end_input =~ ^[0-9]{2}$ ]]; then
    if [[ $start_input -ge 0 && $start_input -le 23 && $end_input -ge 0 && $end_input -le 23 ]]; then
        if [[ $start_input -le $end_input ]]; then
            start_timestamp=$(convert_hours_to_timestamp "$start_input")
            end_timestamp=$(convert_hours_to_timestamp "$end_input")

            # Extract unique dates from log file
            unique_dates=$(awk -F'[][]' '/\[/{print $4}' "$log_file" | sort | uniq)

            start_timestamps=()
            end_timestamps=()
            for date in $unique_dates; do
                start_timestamps+=("${start_timestamp}[$date]")
                end_timestamps+=("${end_timestamp}[$date]")
            done
        else
            echo "Error: start hour must be less than or equal to end hour."
            exit 1
        fi
    else
        echo "Error: Hours must be between 00 and 23."
        exit 1
    fi
else
    timestamp_pattern_checker "$start_input"
    timestamp_pattern_checker "$end_input"
    start_timestamps=("$start_input")
    end_timestamps=("$end_input")
fi

awk -v starts = "${start_timestamps[*]}" -v ends = "${end_timestamps[*]}" '
  function parsedate(date) {
        split(date, a, /[]:[-]+/)
        #ISO 8601 format timestamp conversion
        return a[8] "-" a[7] "-" a[6] "T" a[2] ":" a[3] ":" a[4] "." a[5]
  }
  BEGIN {
    split(starts, start_arr, " ")
    split(ends, end_arr, " ")
    for (i in start_arr) {
        st[i] = parsedate(start_arr[i])
        et[i] = parsedate(end_arr[i])
    }
    log_count = 0
  }
  {
    if (match($0, /^\[[0-9]{2}:[0-9]{2}:[0-9]{2}:[0-9]{3}\]\[[0-9]{2}-[0-9]{2}-[0-9]{4}\]/)) {
        p = parsedate(substr($0, RSTART, RLENGTH))
        in_range = 0
        for (i in st) {
            if (p >= st[i] && p <= et[i]) {
                in_range = 1
                break
            }
        }
    }
    if (in_range) {
        print
        log_count = 1
    }
  }
  END {
    if (log_count == 0) {
        print "Nothing was logged at this given time frame"
    }
  }
' "$log_file"