Как выполнить расчет по лог-файлу

У меня это выглядит так:

I, [2009-03-04T15:03:25.502546 #17925]  INFO -- : [8541, 931, 0, 0]
I, [2009-03-04T15:03:26.094855 #17925]  INFO -- : [8545, 6678, 0, 0]
I, [2009-03-04T15:03:26.353079 #17925]  INFO -- : [5448, 1598, 185, 0]
I, [2009-03-04T15:03:26.360148 #17925]  INFO -- : [8555, 1747, 0, 0]
I, [2009-03-04T15:03:26.367523 #17925]  INFO -- : [7630, 278, 0, 0]
I, [2009-03-04T15:03:26.375845 #17925]  INFO -- : [7640, 286, 0, 0]
I, [2009-03-04T15:03:26.562425 #17925]  INFO -- : [5721, 896, 0, 0]
I, [2009-03-04T15:03:30.951336 #17925]  INFO -- : [8551, 4752, 1587, 1]
I, [2009-03-04T15:03:30.960007 #17925]  INFO -- : [5709, 5295, 0, 0]
I, [2009-03-04T15:03:30.966612 #17925]  INFO -- : [7252, 4928, 0, 0]
I, [2009-03-04T15:03:30.974251 #17925]  INFO -- : [8561, 4883, 1, 0]
I, [2009-03-04T15:03:31.230426 #17925]  INFO -- : [8563, 3866, 250, 0]
I, [2009-03-04T15:03:31.236830 #17925]  INFO -- : [8567, 4122, 0, 0]
I, [2009-03-04T15:03:32.056901 #17925]  INFO -- : [5696, 5902, 526, 1]
I, [2009-03-04T15:03:32.086004 #17925]  INFO -- : [5805, 793, 0, 0]
I, [2009-03-04T15:03:32.110039 #17925]  INFO -- : [5786, 818, 0, 0]
I, [2009-03-04T15:03:32.131433 #17925]  INFO -- : [5777, 840, 0, 0]

Я хотел бы создать сценарий оболочки, который вычисляет среднее значение 2-го и 3-го полей в скобках (840 и 0 в последнем примере). Еще более сложный вопрос: можно ли получить среднее значение 3-го поля только тогда, когда последнее не 0?

Я знаю, что мог бы использовать Ruby или другой язык для создания сценария, но я хотел бы сделать это на Bash. Любые хорошие предложения по ресурсам или подсказки о том, как создать такой сценарий, помогут.


person Julien Genestoux    schedule 04.03.2009    source источник
comment
Было бы тривиально сделать это в awk. Это считается как сделать это в bash?   -  person Eddie    schedule 05.03.2009


Ответы (4)


Публикация ответа, который я вставил вам через IM здесь, просто потому, что это заставляет меня попробовать StackOverflow :)

# replace $2 with the column you want to avg; 
awk '{ print $2 }' | perl -ne 'END{ printf "%.2f\n", $total/$n }; chomp; $total+= $_; $n++' < log
person Community    schedule 05.03.2009

Используйте bash и awk:

cat file | sed -ne 's:^.*INFO.*\[\([0-9, ]*\)\][ \r]*$:\1:p' | awk -F ' *, *' '{ sum2 += $2 ; sum3 += $3 } END { if (NR>0) printf "avg2=%.2f, avg3=%.2f\n", sum2/NR, sum3/NR }'

Пример вывода (для ваших исходных данных):

avg2=2859.59, avg3=149.94

Конечно, вам не нужно использовать cat, он включен туда для удобочитаемости и для иллюстрации того факта, что входные данные могут поступать из любого канала; если вам нужно работать с существующим файлом, запустите sed -ne '...' file | ... напрямую.


ИЗМЕНИТЬ

Если у вас есть доступ к gawk (GNU awk), вы можете устранить необходимость в sed следующим образом:

cat file | gawk '{ if(match($0, /.*INFO.*\[([0-9, ]*)\][ \r]*$/, a)) { cnt++; split(a[1], b, / *, */); sum2+=b[2]; sum3+=b[3] } } END { if (cnt>0) printf "avg2=%.2f, avg3=%.2f\n", sum2/cnt, sum3/cnt }'

Те же замечания относительно cat применить.

Немного пояснений:

  • sed only prints out lines (-n ... :p combination) that match the regular expression (lines containing INFO followed by any combination of digits, spaces and commas between square brackets at the end of the line, allowing for trailing spaces and CR); if any such line matches, only keep what's between the square brackets (\1, corresponding to what's between \(...\) in the regular expression) before printing (:p)
    • sed will output lines that look like: 8541, 931, 0, 0
  • awk uses a comma surrounded by 0 or more spaces (-F ' *, *') as field delimiters; $1 corresponds to the first column (e.g. 8541), $2 to the second etc. Missing columns count as value 0
    • at the end, awk divides the accumulators sum2 etc by the number of records processed, NR
  • gawk делает все одним выстрелом; сначала он проверяет, соответствует ли каждая строка одному и тому же регулярному выражению, переданному в предыдущем примере в sed (за исключением того, что в отличие от sed, awk не требует \ перед круглыми скобками, ограничивающими области или интересы). Если строка совпадает, то, что находится между круглыми скобками, заканчивается в [1], которое мы затем разделяем, используя тот же разделитель (запятая, окруженная любым количеством пробелов) и используем это для накопления. Я ввел cnt вместо того, чтобы продолжать использовать NR, потому что количество обработанных записей NR может быть больше, чем фактическое количество соответствующих записей (cnt), если не все строки имеют форму INFO ... [...comma-separated-numbers...], чего не было в случае sed|awk, поскольку sed гарантировало, что все строки, переданные awk, были актуальны.
person vladr    schedule 04.03.2009
comment
Потрясающий! Спасибо и за пояснения! - person Julien Genestoux; 05.03.2009

Используйте nawk или /usr/xpg4/bin/awk в Solaris.

awk -F'[],]' 'END { 
  print s/NR, t/ct 
  }  
{ 
  s += $(NF-3) 
  if ($(NF-1)) {
    t += $(NF-2)
    ct++
    }
  }' infile
person Dimitre Radoulov    schedule 05.03.2009

Используйте Python

logfile= open( "somelogfile.log", "r" )
sum2, count2= 0, 0
sum3, count3= 0, 0
for line in logfile:
    # find right-most brackets
    _, bracket, fieldtext = line.rpartition('[')
    datatext, bracket, _ = fieldtext.partition(']')
    # split fields and convert to integers
    data = map( int, datatext.split(',') )
    # compute sums and counts
    sum2 += data[1]
    count2 += 1
    if data[3] != 0:
        sum3 += data[2]
        count3 += 1
logfile.close()

print sum2, count2, float(sum2)/count2
print sum3, count3, float(sum3)/count3
person S.Lott    schedule 05.03.2009