먼저 내 문제를 자세히 설명하겠습니다. 사실 정말 쉽습니다. 나는 거대한 .txt 파일(정확히 말하면 300GB)을 가지고 있으며 내 패턴과 일치하는 첫 번째 열의 모든 다른 문자열을 다른 .txt 파일에 넣고 싶습니다.
awk '{print $1}' file_name | grep -o '/ns/.*' | awk '!seen[$0]++' > test1.txt
이것이 내가 시도한 것이고 내가 아는 한 잘 작동하지만 문제는 잠시 후 다음과 같은 오류가 발생한다는 것입니다.
awk: program limit exceeded: maximum number of fields size=32767
FILENAME="file_name" FNR=117897124 NR=117897124
이러한 대용량 파일을 구문 분석하는 데 대한 제안 사항이 있습니까?
답변1
awk
나에게는 32767개 이상의 필드가 발생하는 거대한 선 처럼 들립니다 . 그러나 다음으로는 이것을 재현할 수 없습니다 awk
.
> echo | awk 'BEGIN {for(i=1;i<100000;i++) printf "%d ",i}; { print ""; }' >file
> awk '{ print $50000; }' too_long_line_for_awk.txt
50000
> awk --version
GNU Awk 4.1.0, API: 1.0
장기적으로 사용할 수 있는 더 강력한 도구가 있습니다. 첫 번째 필드의 최대 길이를 결정해야 합니다. 100이라고 가정하면 다음을 시도해 볼 수 있습니다.
cut -b -100 file | awk ...
또한 (그러나 이것은 귀하의 질문과 관련이 없습니다) 귀하의 awk | grep | awk
구성이 의미가 없습니다. 다음과 같이 할 수 있습니다:
awk '$1 ~ "/ns/" {sub("^.*/ns/","/ns/",$1); if( !seen[$1]++ ) print $1}' \
file_name >test1.txt
디버깅 제안
Ramesh가 지적했듯이 문제를 일으키는 선을 찾는 것이 흥미로울 수 있습니다. 문제 행의 번호는 다음 명령으로 인쇄된(또는 파일에 기록된) 숫자 중 하나여야 합니다.
awk '{ print NR;}' | tail -n 1 >crashline.txt
"충돌" 전에 버퍼를 비운 경우 awk
다음 숫자(+1)여야 합니다.
답변2
awk
귀하의 도구에는 필드 수에 제한이 있는 것 같습니다 .
예 mawk
:
field.c
:
/*------- more than 1 fbank needed ------------*/
/*
compute the address of a field with index
> MAX_SPLIT
*/
CELL *
slow_field_ptr(int i)
{
....
if (i > MAX_FIELD)
rt_overflow("maximum number of fields", MAX_FIELD);
....
}
rt_overflow
(에 정의됨 error.c
)은 런타임에 오류 메시지를 생성하는 함수입니다.
/* run time */
void
rt_overflow(const char *s, unsigned size)
{
errmsg(0, "program limit exceeded: %s size=%u", s, size);
rt_where();
mawk_exit(2);
}
그리고 파일에서 size.h
:
#define FBANK_SZ 256
#define FB_SHIFT 8 /* lg(FBANK_SZ) */
#else
#define FBANK_SZ 1024
#define FB_SHIFT 10 /* lg(FBANK_SZ) */
#endif
#define NUM_FBANK 128 /* see MAX_FIELD below */
#define MAX_SPLIT (FBANK_SZ-1) /* needs to be divisble by 3 */
#define MAX_FIELD (NUM_FBANK*FBANK_SZ - 1)
보시다시피 MAX_FIELD
기본값은 입니다 256*128 - 1 = 32767
.
사용하면 gawk
이 문제를 해결할 수 있습니다.
답변3
일반적으로 도구가 전문화될수록 매우 큰 파일을 더 잘 처리할 수 있습니다. 이 파일은 awk에서 처리할 수 있습니다. 내장된 필드 처리를 사용하는 대신 첫 번째 필드만 수동으로 추출하면 됩니다. grep 호출과 두 번째 awk 호출을 하나의 awk 호출로 결합할 수도 있습니다.
awk -F '\n' '
{ sub(/[\t ].*/,"");
if (match($0, "/ns/")) $0 = substr($0,RSTART); else next; }
!seen[$0]++
'
그러나 특수 도구를 사용하여 배관하는 것이 더 빠를 수 있습니다. 필드가 항상 탭 문자를 구분 기호로 사용하는 경우 를 사용 cut
하여 첫 번째 필드를 분리할 수 있습니다. 구분 기호가 공백인 경우 로 설정합니다 cut -d ' '
.
cut -f 1 | grep … | …
또는 sed를 사용하여 처음 두 단계를 수행할 수 있습니다. 이것이 더 빠른지 여부는 cut … | grep …
데이터와 구현에 따라 다릅니다. sed 호출에서 \t
구현이 이해하지 못하는 경우 리터럴 탭으로 바꾸고, \t
구현이 이해하지 못하는 경우 백슬래시 줄 바꿈으로 \n
바꾸세요 .s
sed -n -e 's/[ \t].*//' \
-e 's!/ns/!\n&!' -e 'b' \
-e 's/^.*\n//p'
/ns/
첫 번째 필드가 항상 한 번 발생하는 경우 마지막 발생과 일치하는 다음으로 단순화할 수 있습니다 /ns
.
sed -n -e 's/[ \t].*//' -e 's!.*/ns/!/ns/!p'
마지막 단계로 이동하여 일치하는 항목이 많으면 awk 명령은 많은 메모리를 사용합니다. 출력에서 줄 순서를 변경하는 것이 허용되는 경우 이를 sort -u
대신 사용할 수 있습니다.
cut -f 1 | grep -o '/ns/.*' | sort -u
sed -n -e 's/[ \t].*//' -e 's!.*/ns/!/ns/!p' | sort -u