Я нахожусь в ситуации, когда написал сценарий для извлечения строк из файла журнала в течение указанного периода времени. Этот сценарий работает нормально, пока я не обнаружил, что он печатает только те строки, которые имеют временную метку, зарегистрированную в течение указанного времени, и оставляет строки, которые не имеют временной метки, но зарегистрированы в течение указанного периода времени. Те строки, которые не имеют временной метки, но присутствуют в указанном временном интервале, также должны быть напечатаны. Но я не знаю, как этого добиться.
Ниже приведен файл журнала
[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 поддерживает механизмы подстановки и раскрытия переменных для удобной работы с текстом.
Попробуйте СЭД:
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"