2009-10-28 2 views
1

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

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 

ответ

2

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

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 
+0

Хорошая работа ... Я думал о sed, но я не видел, что искал, но это выглядит великолепно. – bradlis7

+0

2,8 секунды! Это будет работать для меня. – bradlis7

+0

+1 Набежать на помощь! Очень хорошо! –

1

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

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

И, наконец, если скорость действительно остается проблемой, вы можете попробовать perl или python вместо bash. Вы не делаете ничего сумасшедшего здесь, хотя (помимо того, что начинаете процесс подоболочки и даты в каждой строке, которую мы уже выяснили, как избежать), поэтому я не знаю, насколько это поможет.

+0

Ах, хорошее мышление. – bradlis7

+0

Для обработки 70 000 строк все еще требуется более 15 секунд, но grep занимает 0.072 секунды, чтобы сделать все это. Я не знаю, как grep так быстро. Может быть, это потому, что он скомпилирован. – bradlis7

+0

grep не записывает в файл. Вы открываете и закрываете файл для записи 70000 раз. Ответ idimba помогает решить эту сторону проблемы - хотя я думаю, что в этот момент я бы переключил языки. Кажется, гораздо проще писать открыто и близко, чем гадать с файловыми дескрипторами. – Cascabel

1

скелет сценария:

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 
+0

Что это за «& -» магия, о которой вы говорите? Я никогда не видел эти файловые дескрипторы. – bradlis7

+0

Закрыть дескриптор файла. Например, «find/2> & -» будет подавлять все сообщения, напечатанные с помощью find to stderr. См. Http://www.linuxtopia.org/online_books/advanced_bash_scripting_guide/x13082.html – dimba

+0

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

0

Этот скрипт выполняет внутренний цикл 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 
+0

Это довольно творчески с конца месяца, но не так, как я надеялся. Мне кажется, что grep будет неэффективным, хотя результаты, похоже, будут быстрее. – bradlis7

Смежные вопросы