구분 기호가 있는 레코드가 포함된 대용량 텍스트 파일(300MB)이 있습니다 \n\n
. 각 줄은 숫자(필드 레이블/이름)로 시작하고 그 뒤에 탭 문자와 필드 내용/값이 오는 필드입니다.
110 something from record 1, field 110
149 something else
111 any field could be repeatable
111 any number of times
120 another field
107 something from record 2, field 107
149 fields could be repeatable
149 a lot of times
149 I mean a LOT!
130 another field
107 something from record 3
149 something else
각 레코드는 100KB를 초과할 수 없습니다.
다음을 통해 문제가 있는 일부 기록(한도보다 큼)을 찾을 수 있습니다.이 레코드/"단락"에서 줄 끝을 제거합니다.그리고길이를 알아내다:
cat records.txt | awk ' /^$/ { print; } /./ { printf("%s ", $0); } ' | awk '{print length+1}' | sort -rn | grep -P "^\d{6,}$"
다음 중 하나를 통해 잘못된 레코드를 처리할 수 있는 방법을 찾으려고 합니다.
- 한도보다 큰 레코드를 삭제합니다.
- 알려진 문제가 있는 특정 태그(위 예에서는 149)의 모든 항목을 제거합니다. 149 필드로 시작하는 모든 행을 삭제하면 제한을 초과하는 레코드가 없다고 가정해도 됩니다.
아마도 제한에 맞게 특정 필드/태그를 충분히 제거하려면 전체 스크립트가 필요할 것입니다. 마지막 항목을 먼저 삭제하는 것이 좋습니다.
이는 오래된 라이브러리 파일 형식과 관련이 있습니다.ISO 2709.
답변1
문제가 있는 기록만 건너뛰고 싶은 경우:
awk 'BEGIN { ORS=RS="\n\n" } length <= 100*1000' file
이렇게 하면 100,000자 이하의 모든 레코드가 인쇄됩니다.
레코드가 너무 큰 경우 특정 양의 정수로 시작하는 필드를 제거하려면 다음을 수행하세요.
awk -v number=149 'BEGIN { ORS=RS="\n\n"; OFS=FS="\n" }
length <= 100*1000 { print; next }
{
# This is a too long record.
# Re-create it without any fields whose first tab-delimited
# sub-field is the number in the variable number.
# Split the record into an array of fields, a.
nf = split($0,a)
# Empty the record.
$0 = ""
# Go through the fields and add back the ones that we
# want to the output record.
for (i = 1; i <= nf; ++i) {
split(a[i],b,"\t")
if (b[1] != number) $(NF+1) = a[i]
}
# Print the output record.
print
}' file
이전과 마찬가지로 짧은 레코드가 인쇄됩니다. 더 긴 레코드는 삭제되며 첫 번째 탭으로 구분된 하위 필드가 숫자 number
(여기서는 명령줄에서 149로 지정됨)인 모든 필드가 삭제됩니다.
대규모 레코드의 경우 필요하지 않은 필드 없이 레코드가 다시 생성됩니다. 내부 루프는 탭의 필드를 분할하고 탭으로 구분된 첫 번째 하위 필드가 아닌 필드를 추가하여 출력 레코드를 다시 만듭니다 number
.
for (i = 1; i <= nf; ++i) {
split(a[i],b,"\t")
if (b[1] != number) $(NF+1) = a[i]
}
POSIX 사양은 awk
다중 문자 값을 지정하지 않을 때 발생하는 상황을 공개하기 때문에(대부분의 구현에서는 이를 정규식으로 처리함) 엄격하게 일관된 구현 대신 RS
사용할 수 있습니다 . 이렇게 하면 데이터의 여러 빈 행이 더 이상 빈 레코드를 분리하지 않습니다.RS=""; ORS="\n\n"
ORS=RS="\n\n"
awk
답변2
또 다른 awk
방법:
awk -v lim=99999 'BEGIN{RS=""; ORS="\n\n"}\
{while (length()>=lim) {if (!sub(/\n149\t[^\n]*/,"")) break;}} length()<lim' file
149
레코드 길이가 변수에 지정된 제한을 초과하는 경우 lim
제한이 유지되거나 축소가 더 이상 가능하지 않을 때까지(실제 교체 횟수가 0으로 표시됨) "nothing"으로 시작하는 줄이 점차적으로 제거됩니다. 그런 다음 최종 길이가 제한보다 작은 레코드만 인쇄합니다.
피해:첫 번째 줄부터 시작하는 줄을 삭제하므로 149
연속 텍스트의 개별 요소를 형성하면 텍스트를 다소 읽을 수 없게 됩니다.
노트:명시 적이 RS=""
지 않고 지정됨RS="\n\n"
가지고 다닐 수 있는awk
다중 문자 동작이 RS
POSIX 사양에 의해 정의되지 않았기 때문에 "단락 모드"에서 사용되는 방식입니다 . 그러나 만약 있을 수 있다면비어 있는파일에 레코드가 있으면 무시되므로 awk
출력에 나타나지 않습니다. 이것이 원하는 것이 아니라면 아마도 명시적인 RS="\n\n"
표기법을 사용해야 할 것입니다. 대부분의 awk
구현에서는 이를 정규식으로 처리하고 "순진하게" 기대하는 대로 수행합니다.
답변3
레코드 구분 기호를 사용할 때마다 \n\n
Perl 및 단락 모드(from man perlrun
)를 고려하십시오.
-0[octal/hexadecimal]
specifies the input record separator ($/) as an octal or hexadecimal number.
[...]
The special value 00 will cause Perl to slurp files in paragraph mode.
이를 사용하면 다음을 수행할 수 있습니다.
100,000보다 긴 모든 레코드를 삭제합니다.수치(파일 인코딩에 따라 바이트열과 다를 수 있습니다):
perl -00 -ne 'print unless length()>100000' file
처음 100,000자 이후의 모든 문자를 제거하여 100,000자를 초과하는 레코드를 자릅니다.
perl -00 -lne 'print substr($_,0,100000)' file
149
다음으로 시작하는 줄 삭제:perl -00 -pe 's/(^|\n)149\s+[^\n]+//g;' file
149
다음으로 시작하는 줄만 삭제하되, 레코드가 100000자를 초과하는 경우:perl -00 -pe 's/(^|\n)149\s+[^\n]+//g if length()>100000; ' file
레코드가 100,000자를 초과하는 경우
149
레코드가 100,000자 미만이 되거나 149로 시작하는 라인이 더 이상 없을 때까지 149로 시작하는 라인을 삭제합니다.perl -00 -pe 'while(length()>100000 && /(^|\n)149\s/){s/(^|\n)149\s+[^\n]+//}' file
레코드가 100000자를 초과하는 경우
149
레코드가 100000자 미만이거나 149줄이 더 이상 없을 때까지 시작하는 줄을 삭제합니다.아직100,000자를 초과하면 처음 100,000자만 인쇄됩니다.perl -00 -lne 'while(length()>100000 && /(^|\n)149\s/){ s/(^|\n)149\s+[^\n]+// } print substr($_,0,100000)' file
마지막으로 위와 같이 수행하되 레코드가 잘리지 않도록 올바른 크기를 얻을 때까지 문자뿐만 아니라 전체 줄을 삭제합니다.
perl -00 -ne 'while(length()>100000 && /(^|\n)149\s/){ s/(^|\n)149\s+[^\n]+// } map{ $out.="$_\n" if length($out . "\n$_")<=100000 }split(/\n/); print "$out\n"; $out="";' file
답변4
더 우아할 수도 있지만 해결책은 다음과 같습니다.
cat records.txt | awk -v RS='' '{if (length>99999) {gsub(/\n149\t[^\n]*\n/,"\n");print $0"\n"} else {print $0"\n"} }'
나는 고양이의 쓸모없는 용도를 알고 있다고 믿는다왼쪽에서 오른쪽으로의 흐름이 더 명확해졌습니다..
여기서 99999는 임계값 크기이고 149는 이 경우 삭제할 행(필드 이름)의 시작 부분입니다.
나는 non-greedy를 사용 \n149\t[^\n]*\n/
하여 ^149\t.*$
.
gsub
패턴을 지정된 문자열로 바꾸고 대체/대체 횟수를 반환합니다.
그것은 영감을 얻었습니다이 답변.