он может печатать только первое и последнее значение при использовании для l oop для чтения массива в Bash - PullRequest
0 голосов
/ 23 марта 2020

В каталоге dir есть файл rotate gz log, он вращается каждые двадцать минут, используя logrotate с dateformat '.% S' , как потоки

ls -l /var/log/app/h323server.log.[1-9][0-9]*  |head
-rw-r--r-- 1 root adm   2063852 Mar 19 02:00 /var/log/app/h323server.log.1584554401.gz
-rw-r--r-- 1 root adm   2093937 Mar 19 02:20 /var/log/app/h323server.log.1584555601.gz

Я хочу выведите соответствующее содержимое журнала между отметкой времени start_time и отметкой времени end_time , есть несколько шагов:

1, найдите файл журнала и заполните их в массив с именем totalfile

2, используйте для l oop для чтения итогового файла и печати, элемент first и last необходимо фильтровать по начальной и конечной отметке времени, печатать остальную часть файла напрямую. Я хочу использовать for (( i=1; i<${arraylength}+1; i++ )); l oop для достижения этой цели ,, но что-то идет не так.

Сценарий Bash выглядит следующим образом:

#!/bin/bash

oldifs="$IFS"
IFS=$'\n'
declare -a filetime
declare -a filename
declare -a totalfile
index_1=0
index_2=0

for line in $(ls -l /var/log/app/h323server.log.[1-9][0-9]* |awk '{split($NF,a,".");print a[3],$NF}')
do
        filetime[${index_1}]=$(echo ${line} |awk '{print $1}')
        filename[${index_2}]=$(echo ${line} |awk '{print $2}')
        ((index_1++))
        ((index_2++))
done
IFS="$oldifs" 

index=0
timesys_s=1584945601
timesys_e=1584948001

# store the corresponding delaycompress and compress file to totalfile array
while [ ${index} -le $((${#filetime[@]}-1)) ]
do
        if [ ${index} -eq 0 ]
        then
                if [[ ${filetime[${index}]} -ge ${timesys_s} ]] || \
                   [[ ${filetime[${index}]} -le ${timesys_s} ]] || \
                   [[ (${filetime[${index}-1]} -ge ${timesys_s}) && (${filetime[${index}]} -le ${timesys_e}) ]]
                then
                        totalfile[${index}]=${filename[${index}]}
                fi
        else
                if [[ (${filetime[${index}-1]} -le ${timesys_s}) && (${filetime[${index}]} -ge ${timesys_s}) ]] || \
                   [[ (${filetime[${index}-1]} -ge ${timesys_s}) && (${filetime[${index}]} -le ${timesys_e}) ]] || \
                   [[ (${filetime[${index}-1]} -le ${timesys_e}) && (${filetime[${index}]} -ge ${timesys_e}) ]]
                then
                        totalfile[${index}]=${filename[${index}]}
                fi
        fi
        ((index++))
done

echo "length of totalfile:"
echo ${#totalfile[@]}
echo "content of totalfile:"
echo ${totalfile[@]}

# get length of totalfile
arraylength=${#totalfile[@]}

# use for loop to read all values and indexes
for (( i=1; i<${arraylength}+1; i++ ));
do
  echo $i " / " ${arraylength} " : " ${totalfile[$i-1]}
done

# can only print the first and last value when using ${array[index]} to loop
echo "the length of totalfile is: ${arraylength}"
echo "the 1st element: ${totalfile[0]}"
echo "the 2st element: ${totalfile[1]}"
echo "the 3st element: ${totalfile[2]}"
echo "the 4st element: ${totalfile[3]}"
echo "the 5st element: ${totalfile[-1]}"

вывод выглядит следующим образом:

length of totalfile:
5
content of totalfile:
/var/log/app/h323server.log.1584554401.gz /var/log/app/h323server.log.1584945601.gz /var/log/app/h323server.log.1584946801.gz /var/log/app/h323server.log.1584948001.gz /var/log/app/h323server.log.1584949201.gz
1  /  5  :  /var/log/app/h323server.log.1584554401.gz
2  /  5  : 
3  /  5  : 
4  /  5  : 
5  /  5  : 
the length of totalfile is: 5
the 1st element: /var/log/app/h323server.log.1584554401.gz
the 2st element: 
the 3st element: 
the 4st element: 
the 5st element: /var/log/app/h323server.log.1584949201.gz

Вопрос в следующем:

В массиве totalfile есть пять элементов, но только "$ {totalfile [0]}" и "$ {totalfile [-1]}" может печатать нормально, в то время как «$ {totalfile [1]}», «$ {totalfile [2]}» и «$ {totalfile [3]}» не печатает вообще.

Еще одна вещь, когда я использую "$ {totalfile [-4]}", "$ {totalfile [-3]}" и "$ {totalfile [-2]}", это работает.

использование -4, - 3, -2 вместо 1,2,3

echo "the length of totalfile is: ${arraylength}"
echo "the 1st element: ${totalfile[0]}"
echo "the 2st element: ${totalfile[-4]}"
echo "the 3st element: ${totalfile[-3]}"
echo "the 4st element: ${totalfile[-2]}"
echo "the 5st element: ${totalfile[-1]}"

вывод:

the length of totalfile is: 5
the 1st element: /var/log/app/h323server.log.1584554401.gz
the 2st element: /var/log/app/h323server.log.1584945601.gz
the 3st element: /var/log/app/h323server.log.1584946801.gz
the 4st element: /var/log/app/h323server.log.1584948001.gz
the 5st element: /var/log/app/h323server.log.1584949201.gz

Система операционной системы "Ubuntu 14.04.5 LTS".

Я не понимаю, как это происходит. И я буду признателен, если кто-нибудь сможет мне это объяснить.

Ответы [ 2 ]

1 голос
/ 23 марта 2020

Сохранение состояния может быть сложным в bash. Просто проанализируйте поток, как он идет.

start_time='now -2 hour'
stop_time='now -1 hour'

# convert to seconds since epoch
start_time=$(date --date="$start_time" +%s)
stop_time=$(date --date="$stop_time" +%s)

# get list of files
( cd /var/log/app/ && find . -type f -name 'h323server.log.*.gz' ;) |
# extract the number
sed 's/\.\([0-9]*\).gz$/& \1/' |
# compare and print the filename
awk -v start_time="$start_time" -v stop_time="$stop_time" \
     'start_time < $2 && $2 < (stop_time + 20 * 60) { print $1 }' 
# I guess maybe also `(start_time - 20 * 60)` to fetch the previous one

Примечания:

  • Хороший скрипт!
  • Используйте for ((i = 0; i < ${#array[@]}; ++i)) для перебора индексов массива. Или просто for i in ${!array[@]}.
  • Я предпочитаю расширение арифмети c вместо if [[ ${filetime[${index}]} -ge ${timesys_s} ]] Я бы if (( ${filetime[${index}]} >= ${timesys_s} )).

Или, например, получить файл до и после match:

find . -type f -name 'h323server.log.*.gz' |
# extract the number
sed 's/\.\([0-9]*\).gz$/& \1/' |
# sort on numbers
sort -n -k2 |
# important - the input is sorted
# compare and print the filename 
awk -v start_time="$start_time" -v stop_time="$stop_time" '
    # Because i dont want to write  stop_time > $2 && $2 > start_time everrywhere, I cache it in cond variable
    # clear cond variable
    { cond=0 }
    stop_time > $2 && $2 > start_time {
        cond_was_true=1; # remember that at least once the condition was met
        cond=1; # if the condition is met, set cond variable
    }
    # so, if the condition is met
    cond {
        # output the previous line before the match if any
        # if we did not output the previous line yet (oncelast)
        # and the previous line length is not empty
        if (!oncelast && length(last) != 0) {
            # remember that we ouputted the previous line and output it
            oncelast=1
            print last;
        }
        # output the current line
        print $1;
        # there is nothing interesting below
        next;
    }
    # remember the previous line
    # the !cond could be just removed, it want be executed because next above
    !cond { last=$1; }
    # print one more line after the condition is true
    # if the condition was true before
    # but is no longer true
    # then only once output the next line after the condition was met
    cond_was_true && !cond && !once { once=1; print $1; }
'

Если вы хотите напечатать содержимое полученных файлов, добавьте | xargs -d$'\n' zcat в конец скриптов.

После sort -n -k2 вход сортируется используя временные метки. Итак, у нас есть условие stop_time > $2 && $2 > start_time, и меня интересует одна строка до и одна после диапазона, для которого выполняется условие.

Выше я использовал переменную cond, чтобы просто не записывать stop_time > $2 && $2 > start_time снова и снова. Я думаю, я попытаюсь переписать более простую версию, но не проверенную:

awk -v start_time = "$ start_time" -v stop_time = "$ stop_time" '

    stop_time > $2 && $2 > start_time {
        # if the condition wasnt true, output the previous line
        if (!cond_was_true &&
               # and the previous_line is not empty
               length(previous_line) != 0) {
            print last;
        }
        # remember that the condition was true
        cond_was_true = 1;
        # output the current line
        print $1;
    }

    # remember the previous line
    { previous_line = $1; }

    # if the condition was true
    # but is no longer met
    # output the next line
    # but output it only once
    cond_was_true && 
             !(stop_time > $2 && $2 > start_time) &&
             !output_next_line_once { 
         output_next_line_once = 1;
         print $1;
    }
'
0 голосов
/ 23 марта 2020

Я думаю, вы должны использовать readarray вместо a для l oop, чтобы прочитать значения: Как использовать 'readarray' в bash для чтения строк из файла в двумерный массив

Но если вы просто хотите получить первую и последнюю строки, вместо этого, скорее всего, подойдет sed:

$ sed -n -e '1p' -e '$p' /etc/passwd
below cmd output started 2020 Mon Mar 23 08:19:32 AM PDT
root:x:0:0:root:/root:/bin/bash
apacheds:x:124:131::/var/lib/apacheds:/bin/bash

Кстати, что вам нужно для файла журнала с одной строкой в этом? Должна ли она печатать одну и ту же строку дважды?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...