Я нахожусь в ситуации, когда написал сценарий для извлечения строк из файла журнала в течение указанного периода времени. Этот сценарий работает нормально, пока я не обнаружил, что он печатает только те строки, которые имеют временную метку, зарегистрированную в течение указанного времени, и оставляет строки, которые не имеют временной метки, но зарегистрированы в течение указанного периода времени. Те строки, которые не имеют временной метки, но присутствуют в указанном временном интервале, также должны быть напечатаны. Но я не знаю, как этого добиться.
Ниже приведен файл журнала
[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"
гарантированно ли входные данные (start_timestamp
и end_timestamp
) имеют точное совпадение в файле? другими словами, учитывая входной образец, может ли start_timestamp
быть строкой, которой не существует во входном примере, например, start_timestamp='[17:00:00:00][01-03-2024] '
?
что произойдет, если вы найдете совпадение для start_timestamp
, но не для end_timestamp
? вы начинаете печатать, когда видите start_timestamp
, а затем распечатываете остальную часть файла? или в этом случае вы бы ничего не печатали, так как никогда не найдете совпадения для end_timestamp
?
Попробуйте СЭД:
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
Как всегда, это более элегантно и лаконично, чем мое, но не поддерживает использование OP массивов Bash для указания нескольких временных диапазонов. (Конечно, в вопросе ничего не упоминается об этом требовании.)
@triplee, спасибо. Да, я не рассматриваю остальную часть кода OP, а только ту часть, о которой они спрашивали («Извлечение журналов, зарегистрированных в пределах двух временных меток»), поскольку весь их сценарий bash должен быть просто одним сценарием awk, а не настраивать bash. массивы, а затем вызывая awk, и я подозреваю, что многое из того, что они делают, связано только с их текущей реализацией, и на самом деле это не нужно делать, чтобы получить желаемую функциональность, но они не предоставили ни требований, ни достаточного количества образцов входных данных/ вывод, чтобы проверить все это в любом случае, так что это будет совсем другой вопрос, захочет ли ОП заняться этим.
Я бы использовал 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
, неэффективно и потенциально может привести к поломке или очень медленной работе в больших файлах. Другие решения тут же обрабатывают каждую строку, а затем могут забыть об этом.
Наконец, я понял это с помощью ребят, которые пытались. Я многому научился. Большое спасибо, что обратились ко мне, чтобы помочь. Идеальный ответ, который работает для меня, здесь.
#!/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"
Для контекста, предыдущий вопрос: stackoverflow.com/questions/78607470/…