저는 대용량 파일을 작업 중입니다 bash
. 내용은 다음과 같습니다.
- 나는 큰 파일을 가지고 있습니다: 75G와 400,000,000줄이 넘습니다(로그 파일입니다. 안타깝지만 커지게 두었습니다).
- 각 줄의 처음 10자는 YYYY-MM-DD 형식의 타임스탬프입니다.
- 파일을 분할하고 싶습니다. 하루에 한 파일씩.
다음 스크립트를 사용해 보았지만 작동하지 않습니다. 내 문제는 대체 솔루션이 아니라 이 스크립트가 작동하지 않는다는 것입니다..
while read line; do
new_file=${line:0:10}_file.log
echo "$line" >> $new_file
done < file.log
디버깅 후 문제가 new_file
변수에 있다는 것을 발견했습니다. 이 스크립트는 다음과 같습니다.
while read line; do
new_file=${line:0:10}_file.log
echo $new_file
done < file.log | uniq -c
다음 결과를 제공합니다(데이터를 비공개로 유지하기 위해 es를 입력했으며 x
다른 문자는 실제입니다). 참고 dh
및 짧은 문자열:
...
27402 2011-xx-x4
27262 2011-xx-x5
22514 2011-xx-x6
17908 2011-xx-x7
...
3227382 2011-xx-x9
4474604 2011-xx-x0
1557680 2011-xx-x1
1 2011-xx-x2
3 2011-xx-x1
...
12 2011-xx-x1
1 2011-xx-dh
1 2011-xx-x1
1 208--
1 2011-xx-x1
1 2011-xx-dh
1 2011-xx-x1
...
내 파일 형식이 문제가 아닙니다. 스크립트는 cut -c 1-10 file.log | uniq -c
유효한 타임스탬프만 제공합니다. 흥미롭게도 위 출력의 일부는 다음과 같습니다 cut ... | uniq -c
.
3227382 2011-xx-x9
4474604 2011-xx-x0
5722027 2011-xx-x1
4474604
uniq count 후에 초기 스크립트가 실패했음을 알 수 있습니다 .
bash에서 내가 인식하지 못하는 한계에 도달했습니까? bash에서 버그를 발견했습니까(가능성이 낮음). 아니면 뭔가 잘못하고 있습니까?
고쳐 쓰다:
2G 파일을 읽은 후 문제가 발생했습니다. 이음새 read
와 리디렉션은 2G보다 큰 파일을 좋아하지 않습니다. 그러나 더 정확한 설명은 여전히 탐색되고 있습니다.
업데이트 2:
버그처럼 보입니다. 다음과 같이 재현할 수 있습니다.
yes "0123456789abcdefghijklmnopqrs" | head -n 100000000 > file
while read line; do file=${line:0:10}; echo $file; done < file | uniq -c
그러나 이것은 해결 방법으로도 잘 작동합니다(내가 찾은 유용한 사용법인 것 같습니다 cat
).
cat file | while read line; do file=${line:0:10}; echo $file; done | uniq -c
GNU와 Debian에 버그가 접수되었습니다. 영향을 받는 버전은 bash
6.0.4의 Debian Squeeze 6.0.2 및 4.1.5입니다.
echo ${BASH_VERSINFO[@]}
4 1 5 1 release x86_64-pc-linux-gnu
업데이트 3:
내 버그 보고서에 신속하게 응답한 Andreas Schwab에게 감사드립니다. 이 패치는 이러한 부적절한 동작을 해결합니다. 영향을 받는 파일은 다음과 같습니다.lib/sh/zread.c
Giles가 앞서 지적했듯이:
diff --git a/lib/sh/zread.c b/lib/sh/zread.c index 0fd1199..3731a41 100644
--- a/lib/sh/zread.c
+++ b/lib/sh/zread.c @@ -161,7 +161,7 @@ zsyncfd (fd)
int fd; { off_t off;
- int r;
+ off_t r;
off = lused - lind; r = 0;
이 r
변수는 반환 값을 저장하는 데 사용됩니다 lseek
. as는 lseek
파일 시작 부분에서 오프셋을 반환합니다. 오프셋은 2GB를 초과하면 음수이므로 성공해야 할 곳 int
에서 테스트가 실패하게 됩니다 .if (r >= 0)
답변1
Bash에서 일종의 버그를 발견했습니다. 이는 알려진 버그이며 수정되었습니다.
프로그램은 파일의 오프셋을 유한한 크기의 정수 유형 변수로 나타냅니다. 예전에는 int
거의 모든 사람이 사용했는데, int
그 종류가 부호 비트를 포함해 32비트로 제한되어 있어서 -2147483648부터 2147483647까지의 값을 저장할 수 있었다. 이제는 다른다양한 항목의 이름을 입력하세요., off_t
파일의 오프셋을 포함합니다.
기본적으로 off_t
32비트 플랫폼에서는 32비트 유형(최대 허용 2GB)이고, 64비트 플랫폼에서는 64비트 유형(최대 허용 8EB)입니다. 그러나 유형을 off_t
64비트 너비로 전환하고 프로그램이 적절한 함수 구현을 호출하도록 하는 LARGEFILE 옵션을 사용하여 프로그램을 컴파일하는 것이 일반적입니다.lseek
.
32비트 플랫폼에서 bash를 실행하고 있고 bash 바이너리가 대용량 파일 지원으로 컴파일되지 않은 것 같습니다. 이제 일반 파일에서 한 줄을 읽을 때 bash는 내부 버퍼를 사용하여 문자를 일괄적으로 읽어 성능을 향상시킵니다. (자세한 내용은 소스 코드 참조)builtins/read.def
). 줄이 완료되면 bash 호출은 lseek
다른 프로그램이 파일의 위치에 관심을 두는 경우를 대비하여 파일 오프셋을 줄 끝으로 되감습니다. 함수 lseek
에서 호출이 발생합니다.zsyncfc
lib/sh/zread.c
.
소스코드를 자세히 읽어보지는 못했지만, 절대 오프셋이 음수일 때 전환점에서 뭔가 원활하게 일어나지 않는 것이 아닌가 추측하고 있습니다. 따라서 bash가 2GB 표시를 통과한 후 버퍼를 다시 채우면 결국 잘못된 오프셋을 읽게 됩니다.
내 결론이 틀렸고 여러분의 bash가 실제로 64비트 플랫폼에서 실행 중이거나 대용량 파일 지원으로 컴파일된 경우 이는 확실히 버그입니다. 이 사실을 배포판에 보고하거나상류.
어쨌든, 쉘은 이러한 대용량 파일을 처리하는 데 적합한 도구가 아닙니다. 매우 느릴 것입니다. 가능하면 sed를 사용하고, 그렇지 않으면 awk를 사용하십시오.
답변2
무엇이 문제인지는 모르겠지만 정말 복잡합니다. 입력 라인이 다음과 같은 경우:
YYYY-MM-DD some text ...
글쎄요, 그럴 이유가 전혀 없습니다:
new_file=${line:0:4}-${line:5:2}-${line:8:2}_file.log
이미 파일에 보이는 것과 똑같이 보이는 결과를 얻기 위해 많은 하위 문자열 작업을 수행하고 있습니다. 이건 어때?
while read line; do
new_file="${line:0:10}_file.log"
echo "$line" >> $new_file
done
이것은 줄의 처음 10자를 가져옵니다. 완전히 포기 bash
하고 다음을 사용할 수도 있습니다 awk
.
awk '{print > ($1 "_file.log")}' < file.log
$1
이는 날짜(각 행에서 공백으로 구분된 첫 번째 열)를 가져와 이를 사용하여 파일 이름을 생성합니다.
파일에 가짜 로그 줄이 있을 수 있습니다. 즉, 문제는 스크립트가 아니라 입력에 있을 수 있습니다. awk
다음과 같이 잘못된 줄을 표시하도록 스크립트를 확장할 수 있습니다 .
awk '
$1 ~ /[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/ {
print > ($1 "_file.log")
next
}
{
print "INVALID:", $0
}
'
이는 YYYY-MM-DD
로그 파일과 일치하는 행을 작성하고 표준 출력에서 타임스탬프로 시작하지 않는 행을 표시합니다.
답변3
당신이하고 싶은 일은 다음과 같습니다 :
awk '
{ filename = substr($0, 0, 10) "_file.log"; # input format same as output format
if (filename != lastfile) {
close(lastfile);
print 'finished writing to', lastfile;
}
print >> filename;
lastfile=filename;
}' file.log
이렇게 하면 close
열린 파일 테이블이 가득 차는 것을 방지할 수 있습니다.