나는 현재 큰 로그 파일에서 특정 기준과 일치하는 항목을 가져오기 위해 awk를 사용하려고 합니다. 기본적으로 명령에 포함된 정보(일반적으로 명령의 다른 위치에 있을 수 있음)를 기반으로 트랜잭션 ID로 태그된 전체 명령을 추출할 수 있어야 합니다. 아래 샘플 로그(고농축). 전송된 명령은 한 줄일 수도 있고 여러 줄(예: 00001 및 00002)에 걸쳐 있을 수도 있으며 명령이 반드시 함께 그룹화될 필요는 없으며 명령 사이에 다른 ID가 삽입될 수 있습니다.
(NAME, 486, 00001) <xml><command:name>target</command:name></xml>
(NAME, 486, 00001) <response>
(NAME, 486, 00001) <result code="200">
(NAME, 486, 00001) <msg>Command failed</msg>
(NAME, 486, 00001) </result>
(NAME, 486, 00001) </response>
(FOO, 486, 00002) <xml>
(FOO, 486, 00002) <differentCommand:name>This is another sent command</differentCommand:name></xml>
(FOO, 486, 00002) </xml>
(FOO, 486, 00002) <response>
(FOO, 486, 00002) <result code="400">
(FOO, 486, 00002) <msg>Command completed successfully</msg>
(FOO, 486, 00002) </result>
(FOO, 486, 00002) </response>
(ANOTHERNAME, 486, 00003) <xml><command:name>target</command:name></xml>
(ANOTHERNAME, 486, 00003) <response>
(ANOTHERNAME, 486, 00003) <result code="400">
(ANOTHERNAME, 486, 00003) <msg>Command completed successfully</msg>
(ANOTHERNAME, 486, 00003) </result>
(ANOTHERNAME, 486, 00003) </response>
(FOO, 486, 00004) <xml>
(FOO, 486, 00004) <command:name>This is another sent command</command:name></xml>
(FOO, 486, 00004) </xml>
(FOO, 486, 00004) <response>
(FOO, 486, 00004) <result code="400">
(FOO, 486, 00004) <msg>Command completed successfully</msg>
(FOO, 486, 00004) </result>
(FOO, 486, 00004) </response>
기본적으로 응답(괄호 안의 5자리 숫자는 트랜잭션 ID)을 포함하여 전체 명령인 이름을 반환하고 싶지만 성공하는 경우(결과 코드="400")만 반환하고 싶습니다.
이것이 내가 지금까지 가지고 있는 것입니다:
BEGIN { FS="[(,)]"; }
$4 ~ "<command:name" { id[$3] = $3 }
{ for (i in id) {
if ($3 == i) {
if ($5 ~ "Command completed success")
success[i] = i;
}
}
}
$4 in success { print $0 }
하지만 분명히 이건 돌아가지 않을 거야위로검색이 성공하면 항목의 나머지 부분을 얻을 수 있습니다. 다음만 반환됩니다.
(ANOTHERNAME, 486, 00003) <msg>Command completed successfully</msg>
(ANOTHERNAME, 486, 00003) </result>
(ANOTHERNAME, 486, 00003) </response>
(FOO, 486, 00004) <msg>Command completed successfully</msg>
(FOO, 486, 00004) </result>
(FOO, 486, 00004) </response>
BEGIN 문 안에 루프를 넣으려고 했지만 시간이 오래 걸리고 해당 크기의 배열을 사용하려고 하면 메모리 문제가 발생합니다(이 파일은 1GB가 넘습니다).
내가 반환하고 싶은 것은 다음과 같습니다.
(ANOTHERNAME, 486, 00003) <xml><command:name>target</command:name></xml>
(ANOTHERNAME, 486, 00003) <response>
(ANOTHERNAME, 486, 00003) <result code="400">
(ANOTHERNAME, 486, 00003) <msg>Command completed successfully</msg>
(ANOTHERNAME, 486, 00003) </result>
(ANOTHERNAME, 486, 00003) </response>
(FOO, 486, 00004) <xml>
(FOO, 486, 00004) <command:name>This is another sent command</command:name></xml>
(FOO, 486, 00004) </xml>
(FOO, 486, 00004) <response>
(FOO, 486, 00004) <result code="400">
(FOO, 486, 00004) <msg>Command completed successfully</msg>
(FOO, 486, 00004) </result>
(FOO, 486, 00004) </response>
내가 시도하는 것이 awk에서 가능한지 궁금합니다. 나는 한동안 이 작업에 어떤 도구를 사용할지 알아내려고 노력해 왔으며, 내가 아는 한 awk가 단연 최고입니다(Python을 사용해야 하는 것 외에). 속도가 나의 주요 관심사입니다. 오늘의 파일만 일반 텍스트로 사용할 수 있지만(그래서 충분히 빠릅니다), 나머지는 gzip으로 압축되어 있습니다(그래서 그렇게 하고 있습니다 zcat filename | awk -f test.awk
) - 파일을 여러 번 읽는 것을 피하려고 노력하고 있습니다 , 그리고 너무 커서 메모리에 저장할 수 없습니다.
답변1
</response>
이를 레코드 끝 표시 로 사용할 수 있습니다 . 예를 들어:
$ awk -F'[ )]' '{record[$3] = record[$3] "\n" $0};
/<\/response>/ {
if (record[$3] ~ /completed successfully/) {
# optional: remove leading newline if you don't want
# a blank line before each output record:
# sub(/\n/,"",record[$3])
print record[$3]
};
delete record[$3]
}' input.log
(FOO, 486, 00002) <xml>
(FOO, 486, 00002) <differentCommand:name>This is another sent command</differentCommand:name></xml>
(FOO, 486, 00002) </xml>
(FOO, 486, 00002) <response>
(FOO, 486, 00002) <result code="400">
(FOO, 486, 00002) <msg>Command completed successfully</msg>
(FOO, 486, 00002) </result>
(FOO, 486, 00002) </response>
(ANOTHERNAME, 486, 00003) <xml><command:name>target</command:name></xml>
(ANOTHERNAME, 486, 00003) <response>
(ANOTHERNAME, 486, 00003) <result code="400">
(ANOTHERNAME, 486, 00003) <msg>Command completed successfully</msg>
(ANOTHERNAME, 486, 00003) </result>
(ANOTHERNAME, 486, 00003) </response>
(FOO, 486, 00004) <xml>
(FOO, 486, 00004) <command:name>This is another sent command</command:name></xml>
(FOO, 486, 00004) </xml>
(FOO, 486, 00004) <response>
(FOO, 486, 00004) <result code="400">
(FOO, 486, 00004) <msg>Command completed successfully</msg>
(FOO, 486, 00004) </result>
(FOO, 486, 00004) </response>
이는 아래의 sed+perl 및 sed+awk 버전과 유사하지만 이라는 배열의 적절한 요소(즉, id 번호)에 각 행(앞에 개행 문자가 붙음)을 추가하여 각 레코드 자체를 구성합니다 record
. 줄이 표시되면 </response>
"성공적으로 완료됨"과 일치하면 요소를 인쇄한 다음 요소를 삭제합니다.
이는 sed + awk 또는 sed + perl 버전보다 약간 느립니다(왜냐하면각 입력 행을 배열 요소에 추가합니다.sed
- 빈 행을 자주 삽입하는 것보다 더 많은 CPU 리소스를 사용하고 더 많은 메모리를 사용하지만( </response>
행이 있을 때까지 각 레코드가 메모리에 유지되기 때문에) 지나치게 많지는 않습니다. 각 레코드를 메모리에 유지합니다. 필요한 만큼만 삭제한 다음 삭제하세요.
그러나 이 버전은 특정 ID에 대한 레코드가 다른 ID에 대한 레코드와 인터리브되는 경우에도 작동합니다.
다음은 Perl과 동등한 것입니다:
perl -F'[\h)]' -e '
$record{$F[2]} .= $_;
if (/<\/response>/) {
if ($record{$F[2]} =~ /completed successfully/) {
# print blank line between records
print "\n" if $not_first_record++;
print $record{$F[2]}
}
delete $record{$F[2]};
}' input.log
내 테스트(100,000개의 샘플 데이터 복사본이 포함된 120MB 입력 파일 사용)에서는 awk 버전이 거의 두 배 빠른 것으로 나타났습니다. 내 테스트 시스템(고대 AMD Phenom II 1090T)에서 awk 버전은 약 4.6초 만에 실행된 반면, Perl 버전은 약 7.4초가 걸렸습니다.
고쳐 쓰다
최적화된 Perl 버전은 다음과 같습니다.
가로 공백이나 닫는 대괄호( )를 필드 구분 기호로 [\h)]
사용하는 정규식을 사용하는 대신 Perl의 기본 공백 구분 기호를 사용합니다. 세 번째 필드에서 각 레코드의 키를 추출한 다음 마지막 문자( )
)를 자릅니다.
이 버전은 약 3.9초 만에 실행되며 이는 거의 두 배 빠른 속도입니다. 이는 자동 분할 모드 에 정규식을 사용할 -F
때 엄청난 성능 저하를 보여줍니다.
그런데, 레코드에 연관 배열 대신 인덱스 배열을 사용해 보았지만(즉, 문자열 키 @record
대신 숫자 인덱스 사용 %record
) 성능에 눈에 띄는 차이는 없었습니다. 또한 index()
정규식 일치 대신( index($record{$key},"completed successfully")
대신 ) 해당 함수를 사용해 보았지만 $record{$F[2]} =~ /completed successfully/
눈에 띄는 성능 차이는 발생하지 않았습니다.
perl -ane '
chop($key = $F[2]);
$record{$key} .= $_;
if (/<\/response>/) {
if ($record{$key} =~ /completed successfully/) {
print "\n" if $not_first_record++;
print $key, $record{$key};
}
delete $record{$key}
}' input.log
동일한 최적화는 극적으로는 아니지만 awk의 성능도 향상시킵니다.
chop()
awk에는 기능이 없지만 substr()
동일한 작업을 수행하는 데 사용할 수 있습니다.
awk '{
key = substr($3, 1, length($3)-1);
record[key] = record[key] "\n" $0
};
/<\/response>/ {
if (record[key] ~ /completed successfully/) {
sub(/^\n/,"",record[key])
print record[key]
};
delete record[key]
}' input.log
이 버전은 약 3.5초 만에 실행됩니다(이전 awk 버전의 4.6초보다 약 30% 빠릅니다).
전반적으로 업데이트된 awk 및 perl 버전은 성능이 훨씬 더 비슷하지만 awk는 여전히 약 12% 더 빠릅니다.
코드를 조금만 변경하면 성능이 크게 달라질 수 있습니다.
또는:
로그 항목은 항상 이와 같은 ID로 깔끔하게 구분되어 있습니까, 아니면 다른 ID와 인터리브되어 있습니까?
깔끔하게 구분되어 있는 경우 가장 쉬운 방법 중 하나는 sed
빈 줄을 삽입하여 "단락"으로 나누는 것입니다(예: 하나 이상의 빈 줄로 구분).앞으로각 <xml>
라인.
sed
그런 다음 출력은 "단락 모드"에서 로그 로 파이프 awk
되거나 읽혀집니다. perl
awk의 경우 RS=""
BEGIN 블록에서 설정하거나 -v
옵션을 사용하고, Perl의 경우 -00
명령줄 옵션을 사용합니다. 그런 다음 awk 또는 perl 스크립트는 레코드에 "성공적으로 완료됨"이 포함되어 있는지 확인하면 됩니다. 그렇다면 기록을 인쇄하십시오.
위의 awk 전용 버전보다 훨씬 더 빠르게 실행되고 더 적은 메모리를 사용하지만 로깅할 때만 올바르게 작동합니다.아니요다른 레코드와 인터리브되었습니다.
$ sed '/) <xml>/i\\n' input.log |
perl -00 -ne 'print if /completed successfully/m'
(FOO, 486, 00002) <xml>
(FOO, 486, 00002) <differentCommand:name>This is another sent command</differentCommand:name></xml>
(FOO, 486, 00002) </xml>
(FOO, 486, 00002) <response>
(FOO, 486, 00002) <result code="400">
(FOO, 486, 00002) <msg>Command completed successfully</msg>
(FOO, 486, 00002) </result>
(FOO, 486, 00002) </response>
(ANOTHERNAME, 486, 00003) <xml><command:name>target</command:name></xml>
(ANOTHERNAME, 486, 00003) <response>
(ANOTHERNAME, 486, 00003) <result code="400">
(ANOTHERNAME, 486, 00003) <msg>Command completed successfully</msg>
(ANOTHERNAME, 486, 00003) </result>
(ANOTHERNAME, 486, 00003) </response>
(FOO, 486, 00004) <xml>
(FOO, 486, 00004) <command:name>This is another sent command</command:name></xml>
(FOO, 486, 00004) </xml>
(FOO, 486, 00004) <response>
(FOO, 486, 00004) <result code="400">
(FOO, 486, 00004) <msg>Command completed successfully</msg>
(FOO, 486, 00004) </result>
(FOO, 486, 00004) </response>
아니면 awk를 사용하세요:
sed '/) <xml>/i\\n' input.log | awk -v RS='' '/completed successfully/'
이 버전의 출력은 거의 동일하지만 각 출력 레코드 사이에 빈 줄이 없습니다.
개인적으로 각 출력 레코드 사이에 빈 줄이 있으면 이미 "단락 모드"에 있으므로 필요한 경우 출력을 더 쉽게 처리할 수 있으므로 유용하다고 생각합니다. 물론 이것은 주관적인 선호일 뿐이다.