巨大な(70GB)1行のテキストファイルで文字列を置き換える

巨大な(70GB)1行のテキストファイルで文字列を置き換える

私は巨大な(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コードを書くのを防ぎます。特にメモリにファイルをコピーしないように注意してください。これにより、この点を完全に無効にすることができます。利点は、エラーレポート(Python「例外」)を無料で受け取ることができることです:)。

#!/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>

関連情報