DATA 전문가로 가는 길

[Perl] 특정 문자를 찾아서 라인 출력 본문

Programming/Perl

[Perl] 특정 문자를 찾아서 라인 출력

EstenPark 2009. 4. 11. 20:46
문의는 이렇게 왔습니다.
에러가 난 시간대 별로 로그를 보내주세요.

그럼 로그를 보겠습니다.
%Y%m%d-%H:%m [2345345] uniq=[RBADF2353241] R=[-1114^GM]
..
..
%Y%m%d-%H-%m [2345345] USER=[per1]
%Y%m%d-%H-%m [2345345] PASS=[wow]
..
각 세션별로 조금 차이는 있습니다.
자!! 그럼 저위에 보면 R=[-1114]인 코드를 찾아서 user, pass 를 알고 싶다면 어떻게 하실 건가요?
라인이 다르고 어디위치에 있는지 알수도 없고 만약 주기적으로 3번째 밑에 있다고 한다면 방법은 있겠지만 그건 어떠한 상황에 따라 달라지게 됩니다.

본론으로 들어가면.
C-Shell로 만들면 금방 만들지만 제가 perl을 시작했고, 문법을 알고자 삽질을 거듭 거듭...

 
#!/usr/bin/perl

use strict;
use warnings;

my @date = localtime(time) ;
my $today = sprintf("%04d%02d%02d", $date[5] + 1900, $date[4] + 1, $date[3]);

my $log_file = '/estenpark/log/'.$today.'/data.log';

open(FH1, $log_file) || die $!;


my $err_log;

while (<FH1>) {
		if (/\-1114\^GM/) {
			$err_log .= '|'.$_;
		}
}
close FH1;


my @result = split /\|/, $err_log;
my $count = 0;

foreach my $i ( @result ) {
	
	
	my $word_one = substr( $i, 0 , 14);
	my $word_two = substr( $i, 16 , 7 );
	
	print "=================================================== \n";
	print "$count 번째 Time out Messages\n";
	print "--------------------------------------------------- \n";
	print `grep $word_one $log_file |grep $word_two |egrep 'USER|PASS'`;
	print "=================================================== \n\n";
	
	$count++;
}
결과 : 
20090411-19:04 [2345345] USER=[per1] 
20090411-19:04 [2345345] PASS=[wow]
20090411-19:04 [1233322] USER=[am] 
20090411-19:04 [1233322] PASS=[wow]
 
20090411-19:04 [1238043] USER=[per1] 
20090411-19:04 [1238043] PASS=[wow]
  • my $today = sprintf("%04d%02d%02d", $date[5] + 1900, $date[4] + 1, $date[3]);
    현재 날짜를 가지고 오기 위해 위와 같이 localtime(time)의 값을 배열에 값을 출력 했습니다.
  • open(FH1, $log_file) || die $!;
    이부분은 다들아시겠죠? 파일을 읽어 들이기 위해 open함수를 사용 하였습니다.
  • while (<FH1>) {}
    한 라인씩 출력기 위해서 while문을 사용 하였습니다.
  • close FH1;
    파일을 사용하고 무한정 쓸 필요가 없기 때문에 닫습니다.
  • my @result = split /\|/, $err_log;
    스칼라변수에 넣은 값들을 split 함수를 이용하여 '|' 문자열을 나누어 리스트 형태로 반환합니다.
  • foreach my $i ( @result ) {}
    해당 원소의 갯수 만큼 루프를 돌리기 위해 사용 하는 것이고 그것에 대한 값은 $i 스칼라 변수에 있습니다.
  • my $word_one = substr( $i, 0 , 14);
    substr 함수는 문자열에서 필요한 부분을 가져오기 위한 함수이며, substr(문자열, 시작, 끝)으로 사용됩니다.
  • print `grep $word_one $log_file |grep $word_two |egrep 'USER|PASS'`;
    갑자기 펄에서 유닉스 명령어가 나와서 놀래셨나요? 맞습니다. perl으로 빼내려고 하는데 잘되지 않아서 유용하게 사용되는 grep, egrep를 이용하여 조건에 맞는 값을 출력 하기 위해 사용 하였습니다. 첫번째 인자는 날짜이며 두번째 인자는 [0-9{8}]된 숫자 이며 세번째 인자는 egrep을 이용하여 USER, PASS를 찾기 위해 사용 되었습니다.


위의 소스는 그렇게 어려운 작업은 아니지만 조금은 생각이 필요한 부분이 있을 겁니다. 각각에 로그에 따라 정의된 프로세스 룰이 있기 때문에 그 룰의 맞게 변경하면 보다 좋은 프로그램으로 거듭날것입니다.

오늘 미소님과 http://mibbit.com/chat/?server=irc.freenode.org&channel=%23perl-kr 에서 아주 불나게 대화를 나눴습니다. 보다 편하게 아니면 조금더 펄?스럽게 꾸며보고자 자꾸 드리댔습니다.

그래서 나온 소스 입니다.

 소미님의 ID어
#!/usr/bin/perl


use strict;
use warnings;

# CRETED DATE(YYYYMMDD)
my @date = localtime(time) ;
my $today = sprintf("%04d%02d%02d", $date[5] + 1900, $date[4] + 1, $date[3]);

my $log_file = '/estenpark/log/'.$today.'/data.log';

# FILE READ IN
open(my $FH, '<',  $log_file) || die $!;

# CREATE GREP LIST
my @res_list = grep { /\-1114\^GM/ } <$FH>;

# PRINT OUT MATCH RESULT
foreach my $s ( @res_list ) {
my $word_one = substr( $i, 0 , 14);
my $word_two = substr( $i, 16 , 7 );

print "DATE : $word_one \t PID : $word_two \n";

# seet(TEST,0,0) to set the new position in bytes to POSITION
# FILE RESTART
seek($FH, 0, 0);
while (<$FH>) {
if ( m/$word_one/ && m/$word_two/ )
&&
m/Connected\.from|CLIENT\.SND|RECV\.SIGALRM/) {



print;
}
}
}

close $FH;
전체적으로 봐도 깔끔하고 좋은 프로그램이라고 보셔도 됩니다. 다소 아쉬운 점은 속도가 많이 느리고 idel, cpu, mem을 좀 사용한다는 아름답지 못한 점이 발생 되었습니다.
하지만 큰 희망이 생긴건 아주 간결하게 데이터를 뽑아 낼 수 있다는 것입니다.
이부부은 두번 작업해야 되는 경우에 또 open함수로 열것이냐 라는 바보같은 생각을 한번에 바꿔준 고마운 프로그램 입니다. 용량이 크다면 이것을 사용하는 것도 나쁘지 않다고 생각 합니다. 앞서 말한것 처럼 기타 서버의 하드웨어적인 문제가 있긴 하지만 그것은 크게 중점이 될 만한 충격은 아니기 때문입니다.

 소미님의 ID어


#!/usr/bin/perl


use strict;
use warnings;

# CRETED DATE(YYYYMMDD)
my @date = localtime(time) ;
my $today = sprintf("%04d%02d%02d", $date[5] + 1900, $date[4] + 1, $date[3]);

my $log_file = '/estenpark/log/'.$today.'/data.log';

# FILE READ IN
open(my $FH, '<',  $log_file) || die $!;
my @scourt_log = <$FH>;

close $FH;

# CREATE GREP LIST
my @res_list = grep { /\-1114\^GM/ } @scourt_log;

# PRINT OUT MATCH RESULT
foreach my $s ( @res_list ) {
my $word_one = substr( $i, 0 , 14);
my $word_two = substr( $i, 16 , 7 );

print "DATE : $word_one \t PID : $word_two \n";

# seet(TEST,0,0) to set the new position in bytes to POSITION
# FILE RESTART
my @result =
               grep { m/$word_one/ && m/$word_two/ } @scourt_log;
               #|| m/CLIENT.SND|RECV.SIGALRM|Connected.from/

print join '', @result;

}
위의 소스보다는 조금 빠른편이지만 그래도 역시 많은 양을 배열에 넣고 수시로 찾는 이점이 있어서 조금은 아쉬운 점이 발생 되었습니다.
결과는 조금 다르게 나오게 됩니다. grep를 이용해서 해당 라인에서 필요한 정보를 찾는 방법인데요. 여기서 중요한점은 #|| m/CLIENT.SND|RECV.SIGALRM|Connected.from/ 에서 사용하면 제대로 수행이 되지 않습니다.
만약 위의 패턴데로 뽑고 싶다면 grep { m/$word_one/ && m/$word_two/ && m/USER|PASS/ }
로 하시면 됩니다. 각각 다른 { } 곳에 사용하면 전체의 라인이 출력되고 원하는 부분을 출력 할 수가 없어지게 됩니다. 저는 개인적으로 이부분이 굉장이 마음에 들고 간결함은 둘째요. 아무때나 갔다가 쓸 수 있다는 좋은 점도 있습니다. 위에도 그렇지만 아무래도 system에 조금 문제가 되지 않나 생각이 되기도 합니다.

시간만 더 있다면 아주 깨끗하게 써내리고 싶지만 현재 퇴근을...ㅎㅎ

황금같은 주말에 미소님과 허니님 그리고 진님 덕분에 좋은 공부 마쳤습니다.





Comments