LINUX.ORG.RU

маленькая програмка чтобы вытащить числа из лога


0

0

пару недель назад столкнулся с такой проблемой. был лог, и из этого лога нужно было вытащить числа, сложить и посчитать среднее арифметическое. я не нашёл никакой тулзы или редактора который бы мог сделать это автоматом и не нужно было клацать на калькуляторе 150 чисел вручную (хотя не исключено что вим это умеет). вот кусок лога: [demod/stv362/s362lock.c:189][0] s362_Lock() Status[2] Time[500ms] [demod/stv362/s362lock.c:189][0] s362_Lock() Status[2] Time[400ms] [demod/stv362/s362lock.c:189][0] s362_Lock() Status[2] Time[1800ms] [demod/stv362/s362lock.c:189][0] s362_Lock() Status[2] Time[500ms] мне нужно было вытащить эти самые миллисекунды. я решил не заморачиваться поисками и написал такую простенькую программку, которая это делает. в данный момент нет поддержки связанных списков для файлов неопределённого размера, не поддерживается обработка того, что время попадается 2 раза в строке, и нет защиты от переполнения unsigned int переменной. В общем-то хотелось бы послушать какие ещё тут могут быть бока, а также про неоптимальность алгоритма и т.д. и т.п. ;)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


char *version="0.53";
#define CHARS_PER_STRING 300
//#define DEBUG_ENABLE


FILE * file_ptr;
int times[300];  // strings in file

int FileScan(FILE* file_ptr, const char * sequence)
{
	unsigned int i;
	unsigned int sequence_size=0;
	unsigned int total=0;
	unsigned char have_sequence=0;
	char tmp_string[CHARS_PER_STRING];   //array to contain one string
	char * tmp_str_ptr;
  char digit[10];
  int digit_cnt=0;
	unsigned int strings_seq_cnt=0;     //strings with sequence found
	unsigned int strings_total_cnt=0;   //total number of strings in file

	sequence_size=strlen(sequence);
  
#ifdef DEBUG_ENABLE
	printf ("sequence: %s \n", sequence);
	printf("size of seq %d\n", sequence_size);
#endif

	//working till the EOF, one iteration == one string have read
	while ( fgets(tmp_string,CHARS_PER_STRING,file_ptr) != NULL )
	{
		tmp_str_ptr=tmp_string;
		have_sequence=0;
  		while (*tmp_str_ptr)
  		{
    			if (*tmp_str_ptr == *sequence)
    			{
      				if ( !strncmp(tmp_str_ptr,sequence,sequence_size) ) //if we found Time[
      				{
#ifdef DEBUG_ENABLE
					printf("seq found\n");
#endif
		        		tmp_str_ptr += sequence_size ;   //set pointer to time digits
        				while (*tmp_str_ptr >= '0' && *tmp_str_ptr <= '9')
        				{
	  					if (digit_cnt >= 10)
	  					{
	  						printf("[error] value found is too big. \n");
							return 1;
	  					}
          					digit[digit_cnt]=*tmp_str_ptr;
          					tmp_str_ptr++;
          					digit_cnt++;
	  					have_sequence=1;
        				}
#ifdef DEBUG_ENABLE
					printf ("digit %d \n",digit_cnt);
#endif
      				}
    			}
    			tmp_str_ptr++;
  		}

		if (have_sequence)
		{
			times[strings_seq_cnt]=atoi(digit);
			strings_seq_cnt++;
		}
  		for (i=0; i<10; i++)
    			digit[i]='a';
  		strings_total_cnt++;  //increased for every cycle iteration, contains number of strings in file
  		digit_cnt=0;

	}

	for (i=0; i<strings_seq_cnt; i++)
 	{
   		total+=times[i];
   		printf ("string: %d time : %d \n",i+1,times[i]);
 	}
 	printf ("strings total:         %d \n",strings_total_cnt);
	printf ("strings with sequence: %d \n",strings_seq_cnt);
	printf ("time sum:              %d \n",total);
 	if (strings_seq_cnt)
		total/=strings_seq_cnt;
	printf ("time~: %d\n",total);

	return 0;
}


int main (int argc , char* argv[])
{
	unsigned int i;

#ifdef DEBUG_ENABLE
	printf("timefromlog started. version: %s\n",version);
	printf("argc=%d\n",argc);
	for (i=0; i<argc; i++)
	{
		printf("argv[%d]: %s\n",i,argv[i]);
	}

#endif
	//parsing input without getopt() ! 
	if (argc == 2)
	{
		if ( !(strcmp(argv[1],"--help") ) )  
			printf ("this is a help for timefromlog program.\nfirst param: filename to search in.\nsecond param: sequence digits start after.\n --help    - this help\n --version - program version\n");
		else if ( !(strcmp(argv[1],"--version") ) )  
			printf ("version: %s  \n", version);
		else
			printf("incorrect params. run program with --help\n");
		return 1;
	}       
	else if (argc != 3)
	{
		printf("incorrect params. run program with --help\n");
		return 1;
	}

	file_ptr=fopen(argv[1],"r");
	if (!file_ptr)
	{
		printf("[error] : can't open file\n");
		return 1;
	}
	else 
		printf("[working] : file opened\n");

	FileScan(file_ptr, argv[2]);
	
	if ( !fclose(file_ptr) )
		printf("[working] : file closed successfully\n");
	else 
		printf("[error] : file can not be closed\n");
  

  return 0;
}

есть такая тулза, называется perl

Reset ★★★★★
()

А в чем профит С в данной задаче?
Например на перле, без всех твоих ограничений, примерно так:

#!/usr/bin/perl

use strict;

my $file = shift;
my $sum = 0;
my $count = 0;

open(FILE, "<", $file)
or die("Can't open file $file: " . $!);

while (my $line = <FILE>) {
while ($line =~ /Time\[(\d+)ms\]/g) {
$sum += $1;
$count++;
}
}

close(FILE);

print "$sum/$count=" . $sum/$count . "\n";

tzukko
()
Ответ на: комментарий от tzukko

$count на ноль не проверил, так меня впечатлила твоя маленькая программка.
Ты R00T'а случаем не знаешь?

tzukko
()

Надо бы тоже завести себе фейковую асеч^Wаккаунт и троллить местных лиспофагов сиплюсплюсом, а остальных - турбопаскалем и вба.

mannaz
()

Очень рекомендую почитать что-нибудь про стандартные утилиты (coreutils, moreutils, sed, awk и т.д.), а так же про программирование шелл-скриптов.

Вот заменитель для всей твоей программы:

num=$(( $( cat logfile | grep -oe "\[[0-9]*ms\]" | wc -l ) )); cat logfile | grep -oe "\[[0-9]*ms\]" | ( printf "("; sed "s/^\[\([0-9]*\)ms\]$/\1+/" | tr -d "\n"; echo "0)/${num}" ) | bc -l

Deleted
()
Ответ на: комментарий от mannaz

>Надо бы тоже завести себе фейковую асеч^Wаккаунт и троллить местных лиспофагов сиплюсплюсом, а остальных - турбопаскалем и вба.

Да, не стоит - быстро приестся.

tzukko
()
Ответ на: комментарий от Deleted

> num=$(( $( cat logfile | grep -oe "\[[0-9]*ms\]" | wc -l ) )); cat logfile | grep -oe "\[[0-9]*ms\]" | ( printf "("; sed "s/^\[\([0-9]*\)ms\]$/\1+/" | tr -d "\n"; echo "0)/${num}" ) | bc -l

на perl всё ж проще и без лишнего cat,grep:

cat logfile | perl -e '$lc=$lm=$sum=0; while(<>){ if( /Time\[(\d+)ms\]/ ) { $sum+=$1; $lm++; } $lc++; } print "total lines: $lc\nmatched: $lm\nsumm: $sum\n"; print "avg: ".( $sum / $lm )."\n" if( $lm );'

FELiX
()
Ответ на: комментарий от Deleted

> Вот заменитель для всей твоей программы:

> num=$(( $( cat logfile | grep -oe "\[[0-9]*ms\]" | wc -l ) )); cat logfile | grep -oe "\[[0-9]*ms\]" | ( printf "("; sed "s/^\[\([0-9]*\)ms\]$/\1+/" | tr -d "\n"; echo "0)/${num}" ) | bc -l


А на lex'е-то покороче будет... Тем более, автор так Си любит ;)

mv ★★★★★
()
Ответ на: комментарий от mv

Вот вариант на lex'е:

%{
	int total = 0;
%}
%%
Time\[([0-9]+)ms\]	total += atoi(yytext + 5);
.
%%

int yywrap ()
{
	return 1;
}

int main()
{
	yylex();
	printf("total = %d\n", total);
	return 0;
}

Автор, не пиши парсеры на Си!

mv ★★★★★
()
Ответ на: комментарий от mv

Предлагаю придумать максимально короткий вариант на любом Я.П.! Имя файла с логами - logfile. Программа должна просто вывести число (среднее значение). Производительность значения не имеет.

Мой вариант на шелле:

C="grep -o '\[[0-9]*ms\]' logfile";n=$(eval $C|wc -l);eval $C|(printf "(";grep -o "[0-9]*"|tr "\n" "+";echo "0)/$n")|bc -l

123 символа =).

Deleted
()
Ответ на: комментарий от Deleted

C="grep -o '\[[0-9]*ms\]' logfile";eval $C|(echo "($(grep -o "[0-9]*"|tr "\n" "+")0)/$(eval $C|wc -l)")|bc -l

109, подозреваю что меньше - никак.

Deleted
()

Проще завести тсудента-практиканта, а лучше практикантку.

wfrr ★★☆
()
Ответ на: комментарий от Deleted

отпусти меня, чудо-трава!!!11

C="grep -o \[[0-9]*ms\] logfile";eval $C|(echo "($(grep -o [0-9]*|tr "\n" +)0)/$(eval $C|wc -l)")|bc -l

103!!!

Deleted
()
Ответ на: комментарий от Deleted

> Имя файла с логами - logfile.

Лишняя сущность. Лог брать из stdin.

mv ★★★★★
()
Ответ на: комментарий от summatus

народ вы молодцы! ) 1. мне очень понравилась притча. она подходит к ситуации идеально ;) 2. честно говоря ожидал услышать кучу наездов на код, и в общем-то интересно было про алгоритм, но все пошли другим путём. 3. ссылка на аглицкий форум - там тоже мой пост.

какой я для себя сделал вывод - нужно восполнить пробел и хорошо разобраться в шелл скриптах, чтобы не страдать такой фигнёй )

repu1sion
() автор топика
Ответ на: комментарий от repu1sion

#include <QFile>
#include <QRegExp>
#include <stdio.h>

int main() {
	QFile f;
	QRegExp r( "(\\d+)ms" );
	f.open( stdin, QFile::ReadOnly );
	int a=0, b=0, c;
	while( !f.atEnd() ) {
		c = 0;
		QString l = f.readLine();
		while( ( c = r.indexIn( l , c ) ) != -1 ) {
			a += r.cap(1).toInt();
			b++;
			c += r.matchedLength();
		}
	}
	printf( "%d\n", a/b );
	return 0;
}

summatus
()
Ответ на: комментарий от sdio

> 61
> grep -Eo '[0-9]+ms' logfile|awk -Fm '{a+=$1};END{print a/NR}'


Можно сэкономить ещё два символа - убрать кавычки у шаблона grep'а =).

Deleted
()
Ответ на: комментарий от Deleted

Не, шелл может подставить файл, если таковой будет в тек.директории, по шаблону [0-9]+ms

sdio ★★★★★
()
Ответ на: комментарий от repu1sion

Желаете наезды на код? Ладно покормлю вас, раз так старались.

>нет поддержки связанных списков для файлов неопределённого размера

а зачем вы вобще записываете числа в массив? Нашли число, сразу печатайте его.

>printf("[error] value found is too big. \n");

Сообщения об ошибках принято выводить на stderr. Незнаю зачем, но так принято.

>нужно восполнить пробел и хорошо разобраться в шелл скриптах

Ещё можете восполнить пробел в библиотечных функция GNU libc, например, strstr() --- поиск подстроки в строке и strtol() --- аналог atoi, но установит errno при большом числе и даёт указатель на первую "не цифру" в строке.

mky ★★★★★
()
Ответ на: комментарий от sdio

>grep -Eo '[0-9]+ms' logfile|awk -Fm '{a+=$1};END{print a/NR}'

А точку с запятой перед END разве нельзя убрать? И пробел перед awk-скриптом.

anonymous4
()
Ответ на: комментарий от sdio

grep -Eo '[0-9]+ms' logfile|awk -Fm '{a+=$1};END{print a/NR}'

можно сократить до

egrep -o '[0-9]+ms' logfile|awk '{a+=$1}END{print a/NR}'

www_linux_org_ru ★★★★★
()

Дайте уж и я внесу свою лепту =) GNU Smalltalk:

| f t c|
t := 0.0.
c := 0.
f := (File name: 'logfile.txt') readStream.
f linesDo: [:line |
    time := (line tokenize: ' ') last.
    t := t + (time copyFrom: ((time indexOf: $[) + 1)
                         to: ((time indexOf: $m
                                 startingAt: (time indexOf: $[)) - 1))
             asInteger.
    c := c + 1.
].
f close.
(t / c) print.

yoghurt ★★★★★
()
Ответ на: комментарий от yoghurt

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

repu1sion
() автор топика
Ответ на: комментарий от repu1sion

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

Это последовательность добавляется в шаблон grep'а.

Deleted
()
Ответ на: комментарий от repu1sion

Ну, я отталкивался и тестил всё на том примере что с того другого форума. GNU Smalltalk тоже поддерживает регекспы кстати, да, и эту последовательность можно так же легко задать как и в предыдущих однострочниках

yoghurt ★★★★★
()
Ответ на: комментарий от mky

> Незнаю зачем, но так принято.

затем, что stderr не буферизуется и если случится страшное, то это ты сразу увидешь

Reset ★★★★★
()
Ответ на: комментарий от repu1sion

>числа заносятся в массив чтобы посчитать сумму

И сумму считать можно сразу, массив или динамический список не нужен.

И ещё, ИМХО, сообщение вашей программы "this is a help for timefromlog program.\nfirst param: ..." сильно отличаются от общего стиля утилит командной строки. Позапускайте другие утилиты и посмотрите как они "ругаются".

ИМХО, лучше было бы что-то вида "Usage: timefromlog FILE STRPEFIX\nSearch time strings with STRPEFIX in FILE and print there sum and mean." Понятно, что это вы писали для себя, но лучше сразу стараться соблюдать общий стиль.

mky ★★★★★
()
Ответ на: комментарий от mv

гениальный у меня /dev/random, правда? ;) скоро он напишет скайнет и захватит мир :Р

arsi ★★★★★
()
Ответ на: комментарий от arsi

А мой императивный быдлокод на Смолтоке без регекспов всё равно читабельнее и понятнее чем эти ваши однострочники)))

yoghurt ★★★★★
()
Ответ на: комментарий от arsi

Твой вариант

perl -E '$b+=s/\d+ms/$a+=$&/ge for<>;say$a/$b' logfile

у меня не пошел -- опции Е нет и say ругается, так что вот:

perl -e'map{$b+=s/\d+ms/$a+=$&/ge}<>;print$a/$b' logfile

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от mv

> Какой забавный выхлоп из /dev/random, все байты - printable...

Когда я не знаю, как решить задачу на перле, запускаю strings /dev/urandom | less и задумчиво листаю... а вы с вашими неправильными языками такой подсказки лишены!

www_linux_org_ru ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.