거대한(70GB) 한 줄 텍스트 파일에서 문자열 바꾸기

거대한(70GB) 한 줄 텍스트 파일에서 문자열 바꾸기

나는 거대한 (70GB)을 가지고 있습니다.한 줄, 문자열(토큰)을 대체하려는 텍스트 파일입니다. <unk>토큰을 다른 더미 토큰으로 교체하고 싶습니다 (장갑 문제).

나는 시도했다 sed:

sed 's/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new

그러나 출력 파일 corpus.txt.new에는 0바이트가 있습니다!

나는 또한 Perl을 사용해 보았습니다.

perl -pe 's/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new

그런데 메모리 부족 오류가 발생했습니다.

더 작은 파일의 경우 위의 두 명령이 모두 작동합니다.

이와 같은 파일에서 문자열을 어떻게 바꿀 수 있습니까? 이것관련 질문이지만 답변 중 어느 것도 나에게 효과적이지 않았습니다.

편집하다:파일을 10GB(또는 다른) 크기의 청크로 분할하고 sed각 청크에 적용한 다음 와 병합하는 것은 어떻습니까 cat? 말이 돼? 더 우아한 솔루션이 있습니까?

답변1

이러한 대용량 파일의 경우 Flex를 사용할 수 있습니다. 설정 unk.l:

%%
\<unk\>     printf("<raw_unk>");  
%%

그런 다음 컴파일하고 실행합니다.

$ flex -o unk.c  unk.l
$ cc -o unk -O2 unk.c -lfl
$ unk < corpus.txt > corpus.txt.new

답변2

일반적인 텍스트 처리 도구는 RAM에 맞지 않는 줄을 처리하도록 설계되지 않았습니다. 그들은 레코드(행)를 읽고, 이에 대해 작업하고, 결과를 출력한 후 다음 레코드(행)로 이동하는 방식으로 작업하는 경향이 있습니다.

ASCII 문자가 파일에 자주 나타나지만 <unk>또는 에는 나타나지 않는 경우 <raw_unk>이를 레코드 구분 기호로 사용할 수 있습니다. 대부분의 도구는 사용자 정의 레코드 구분 기호를 허용하지 않으므로 이 문자와 개행 문자를 서로 바꾸십시오. tr행이 아닌 바이트를 처리하므로 레코드 크기에 신경 쓰지 않습니다. 유효한 것으로 가정 ;:

<corpus.txt tr '\n;' ';\n' |
sed 's/<unk>/<raw_unk>/g' |
tr '\n;' ';\n' >corpus.txt.new

검색 텍스트에서 반복되지 않고 충분히 자주 발생한다는 가정하에 검색 중인 텍스트의 첫 번째 문자를 고정할 수도 있습니다. 파일이 로 시작하는 경우 가짜 일치를 피하기 unk>위해 sed 명령을 변경하십시오 sed '2,$ s/….

<corpus.txt tr '\n<' '<\n' |
sed 's/^unk>/raw_unk>/g' |
tr '\n<' '<\n' >corpus.txt.new

또는 마지막 문자를 사용하십시오.

<corpus.txt tr '\n>' '>\n' |
sed 's/<unk$/<raw_unk/g' |
tr '\n>' '>\n' >corpus.txt.new

이 기술은 sed가 줄바꿈으로 끝나지 않는 파일에서 원활하게 작동한다고 가정합니다. 즉, 줄을 자르거나 마지막 줄바꿈을 추가하지 않고 줄의 마지막 부분을 처리합니다. GNU sed와 함께 작동합니다. 파일의 마지막 문자를 레코드 구분 기호로 선택할 수 있으면 이식성 문제를 피할 수 있습니다.

답변3

그러니까 너로는 부족해물리적메모리(RAM)는 전체 파일을 한 번에 저장할 수 있지만 64비트 시스템에서는 RAM이 충분합니다.가상전체 파일을 매핑하는 주소 공간. 이 경우 가상 매핑은 쉬운 해킹 역할을 할 수 있습니다.

필요한 작업은 Python에 포함되어 있습니다. 몇 가지 짜증나는 미묘함이 있지만 C 코드 작성을 방지합니다. 특히 메모리에 파일을 복사하지 않도록 주의해야 합니다. 그렇게 하면 이 점을 완전히 무효화할 수 있습니다. 장점으로는 오류 보고서(파이썬 "예외")를 무료로 받을 수 있다는 것입니다 :).

#!/usr/bin/python3
# This script takes input from stdin
# (but it must be a regular file, to support mapping it),
# and writes the result to stdout.

search = b'<unk>'
replace = b'<raw_unk>'


import sys
import os
import mmap

# sys.stdout requires str, but we want to write bytes
out_bytes = sys.stdout.buffer

mem = mmap.mmap(sys.stdin.fileno(), 0, access=mmap.ACCESS_READ)
i = mem.find(search)
if i < 0:
    sys.exit("Search string not found")

# mmap object subscripts to bytes (making a copy)
# memoryview object subscripts to a memoryview object
# (it implements the buffer protocol).
view = memoryview(mem)

out_bytes.write(view[:i])
out_bytes.write(replace)
out_bytes.write(view[i+len(search):])

답변4

내 생각에는 C 버전이 더 나은 성능을 발휘할 수 있을 것 같습니다.

#include <stdio.h>
#include <string.h>

#define PAT_LEN 5

int main()
{
    /* note this is not a general solution. In particular the pattern
     * must not have a repeated sequence at the start, so <unk> is fine
     * but aardvark is not, because it starts with "a" repeated, and ababc
     * is not because it starts with "ab" repeated. */
    char pattern[] = "<unk>";          /* set PAT_LEN to length of this */
    char replacement[] = "<raw_unk>"; 
    int c;
    int i, j;

    for (i = 0; (c = getchar()) != EOF;) {
        if (c == pattern[i]) {
            i++;
            if (i == PAT_LEN) {
                printf("%s", replacement);
                i = 0;
            }
        } else {
            if (i > 0) {
                for (j = 0; j < i; j++) {
                    putchar(pattern[j]);
                }
                i = 0;
            }
            if (c == pattern[0]) {
                i = 1;
            } else {
                putchar(c);
            }
        }
    }
    /* TODO: fix up end of file if it ends with a part of pattern */
    return 0;
}

편집: 의견에서 제안한 대로 수정되었습니다. 또한 모드 버그가 수정되었습니다 <<unk>.

관련 정보