Упорядочить записи журнала в датированные файлы - PullRequest
1 голос
/ 28 октября 2009

Я пытаюсь разделить большой файл журнала, содержащий записи журнала по месяцам, и пытаюсь разделить его на файлы журнала по дате. Есть тысячи строк следующим образом:

Sep 4 11:45 kernel: Entry
Sep 5 08:44 syslog: Entry

Я пытаюсь разделить его так, чтобы файлы, logfile.20090904 и logfile.20090905 содержали записи.

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

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

Вот мое текущее решение:

#! /bin/bash
cat $FILE | while read line; do
  dts="${line:0:6}"
  dt="`date -d "$dts" +'%Y%m%d'`"
  # Note that I could do some caching here of the date, assuming
  # that dates are together.
  echo $line >> $FILE.$dt 2> /dev/null
done

Ответы [ 4 ]

2 голосов
/ 29 октября 2009

@ OP стараться не использовать цикл чтения bash для итерации большого файла. Он опробовал и доказал, что это медленно, и, кроме того, вы вызываете внешнюю команду даты для каждой строки файла, который вы читаете. Вот более эффективный способ, используя только gawk

gawk 'BEGIN{
 m=split("Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec",mth,"|")     
}
{ 
 for(i=1;i<=m;i++){ if ( mth[i]==$1){ month = i } }
 tt="2009 "month" "$2" 00 00 00" 
 date= strftime("%Y%m%d",mktime(tt))
 print $0 > FILENAME"."date
}
' logfile

выход

$ more logfile
Sep 4 11:45 kernel: Entry
Sep 5 08:44 syslog: Entry

$ ./shell.sh

$ ls -1 logfile.*
logfile.20090904
logfile.20090905

$ more logfile.20090904
Sep 4 11:45 kernel: Entry

$ more logfile.20090905
Sep 5 08:44 syslog: Entry
1 голос
/ 28 октября 2009

Скелет скрипта:

BIG_FILE=big.txt

# remove $BIG_FILE when the script exits
trap "rm -f $BIG_FILE" EXIT

cat $FILES > $BIG_FILE || { echo "cat failed"; exit 1 }

# sort file by date in place
sort -M $BIG_FILE -o $BIG_FILE || { echo "sort failed"; exit 1 }

while read line;
   # extract date part from line ...
   DATE_STR=${line:0:12} 

   # a new date - create a new file
   if (( $DATE_STR != $PREV_DATE_STR)); then 
       # close file descriptor of "dated" file
       exec 5>&- 
       PREV_DATE_STR=$DATE_STR

       # open file of a "dated" file for write
       FILE_NAME= ... set to file name ...
       exec 5>$FILE_NAME || { echo "exec failed"; exit 1 }
   fi

   echo -- $line >&5 || { echo "print failed"; exit 1 }
done < $BIG_FILE
1 голос
/ 28 октября 2009

Самое быстрое из того, что вы уже сделали, это просто назвать файлы «4 сентября» и так далее, а затем переименовать их все в конце - таким образом, все, что вам нужно сделать, это прочитать определенное количество символов , без дополнительной обработки.

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

Наконец, если скорость действительно продолжает вызывать проблемы, вы можете попробовать Perl или Python вместо Bash. Здесь вы не делаете ничего слишком сумасшедшего (кроме запуска подоболочек и обработки даты каждой строкой, которую мы уже выяснили, как избежать), поэтому я не знаю, насколько это поможет.

0 голосов
/ 29 октября 2009

Этот сценарий выполняет внутренний цикл 365 или 366 раз, по одному разу для каждого дня года, вместо итерации по каждой строке файла журнала:

#!/bin/bash
month=0
months=(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
for eom in 31 29 31 30 31 30 31 31 30 31 30 31
do
    (( month++ ))
    echo "Month $month"
    if (( month == 2 ))    # see what day February ends on
    then
        eom=$(date -d "3/1 - 1 day" +%-d)
    fi
    for (( day=1; day<=eom; day++ ))
    do
        grep "^${months[$month - 1]} $day " dates.log > temp.out
        if [[ -s temp.out ]]
        then
            mv temp.out file.$(date -d $month/$day +"%Y%m%d")
        else
            rm temp.out
        fi
        # instead of creating a temp file and renaming or removing it,
        # you could go ahead and let grep create empty files and let find
        # delete them at the end, so instead of the grep and if/then/else
        # immediately above, do this:
        # grep --color=never "^${months[$month - 1]} $day " dates.log > file.$(date -d $month/$day +"%Y%m%d")
    done
done
# if you let grep create empty files, then do this:
# find -type f -name "file.2009*" -empty -delete
...