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

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

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

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

Для контекста, предыдущий вопрос: stackoverflow.com/questions/78607470/…

tripleee 13.06.2024 15:11

гарантированно ли входные данные (start_timestamp и end_timestamp) имеют точное совпадение в файле? другими словами, учитывая входной образец, может ли start_timestamp быть строкой, которой не существует во входном примере, например, start_timestamp='[17:00:00:00][01-03-2024] '?

markp-fuso 13.06.2024 15:17

что произойдет, если вы найдете совпадение для start_timestamp, но не для end_timestamp? вы начинаете печатать, когда видите start_timestamp, а затем распечатываете остальную часть файла? или в этом случае вы бы ничего не печатали, так как никогда не найдете совпадения для end_timestamp?

markp-fuso 13.06.2024 15:19
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
121
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

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

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

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

Хотя технически это не соответствует действительности, на самом деле это не может решить проблему ФП каким-либо значимым способом. Вам нужно будет точно знать, какие временные метки существуют в файле, или создать весьма нетривиальное регулярное выражение, чтобы идентифицировать интересные строки. И даже в этом случае, что, если последняя строка диапазона, который вы хотите напечатать, имеет содержимое, идентичное содержимому других строк, которые не следует печатать? Конечно, это можно сделать; но в принципе не так.

tripleee 14.06.2024 07:29

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

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

Больно вводить даты в таком ужасном формате. Возможно, хорошим упражнением будет создание копии файла с отметками даты, зафиксированными в машиночитаемом формате, для дальнейших экспериментов.

tripleee 13.06.2024 15:28

Не пытайтесь использовать для этого 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

Как всегда, это более элегантно и лаконично, чем мое, но не поддерживает использование OP массивов Bash для указания нескольких временных диапазонов. (Конечно, в вопросе ничего не упоминается об этом требовании.)

tripleee 14.06.2024 06:09

@triplee, спасибо. Да, я не рассматриваю остальную часть кода OP, а только ту часть, о которой они спрашивали («Извлечение журналов, зарегистрированных в пределах двух временных меток»), поскольку весь их сценарий bash должен быть просто одним сценарием awk, а не настраивать bash. массивы, а затем вызывая awk, и я подозреваю, что многое из того, что они делают, связано только с их текущей реализацией, и на самом деле это не нужно делать, чтобы получить желаемую функциональность, но они не предоставили ни требований, ни достаточного количества образцов входных данных/ вывод, чтобы проверить все это в любом случае, так что это будет совсем другой вопрос, захочет ли ОП заняться этим.

Ed Morton 14.06.2024 13:48

Я бы использовал 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)

Чтение всего файла в память, чтобы вы могли снова его просмотреть в END, неэффективно и потенциально может привести к поломке или очень медленной работе в больших файлах. Другие решения тут же обрабатывают каждую строку, а затем могут забыть об этом.

tripleee 14.06.2024 07:25
Ответ принят как подходящий

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

#!/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"

Другие вопросы по теме