Замените строку в огромной (70 ГБ), одной строке, текстовом файле

У меня есть огромная (70 ГБ), одна строка , текстовый файл, и я хочу заменить в нем строку (токен). Я хочу заменить токен <unk> с другим фиктивным маркером ( вопрос о перчатке ).

Я пробовал sed:

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

, но выходной файл corpus.txt.new имеет нулевые байты!

Я также пробовал использовать perl:

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

, но у меня возникла ошибка в памяти.

Для более мелких файлов работают обе указанные команды.

Как заменить строку таким файлом? Этот является связанным вопросом, но ни один из ответов не работал для меня.

Edit : Как насчет разбивки файла на куски размером 10 ГБ (или любого другого) и применения sed для каждого из них, а затем слияния с cat? Имеет ли это смысл? Есть ли более элегантное решение?

123 голоса | спросил Christos Baziotis 29 FriEurope/Moscow2017-12-29T17:58:33+03:00Europe/Moscow12bEurope/MoscowFri, 29 Dec 2017 17:58:33 +0300 2017, 17:58:33

13 ответов


106

Обычные инструменты обработки текста не предназначены для обработки строк, которые не подходят в ОЗУ. Они, как правило, работают, читая одну запись (одну строку), манипулируя ею и выводя результат, затем переходя к следующей записи (строке).

Если есть символ 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. Если вы можете выбрать последний символ файла в качестве разделителя записей, вы можете избежать любых проблем с переносимостью.

ответил Gilles 29 FriEurope/Moscow2017-12-29T18:07:42+03:00Europe/Moscow12bEurope/MoscowFri, 29 Dec 2017 18:07:42 +0300 2017, 18:07:42
108

Для такого большого файла одной возможностью является 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
ответил JJoao 29 FriEurope/Moscow2017-12-29T19:40:53+03:00Europe/Moscow12bEurope/MoscowFri, 29 Dec 2017 19:40:53 +0300 2017, 19:40:53
39

Таким образом, вам не хватает памяти physical (RAM) для одновременного хранения всего файла, но в 64-битной системе у вас достаточно адресного пространства virtual для отображения всего файла. Виртуальные сопоставления могут быть полезны в качестве простого взлома в таких случаях.

Все необходимые операции включены в 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):])
ответил sourcejedi 30 SatEurope/Moscow2017-12-30T00:44:07+03:00Europe/Moscow12bEurope/MoscowSat, 30 Dec 2017 00:44:07 +0300 2017, 00:44:07
17

Я думаю, что версия 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;
}

EDIT: Изменено в соответствии с предложениями из комментариев. Также исправлена ​​ошибка с шаблоном <<unk>.

ответил Patrick Bucher 29 FriEurope/Moscow2017-12-29T23:14:52+03:00Europe/Moscow12bEurope/MoscowFri, 29 Dec 2017 23:14:52 +0300 2017, 23:14:52
16

В пакете mariadb-server /mysql-server есть утилита replace. Он заменяет простые строки (не регулярные выражения) и в отличие от grep /sed /awk replace не заботится о \n и \0. Потребление памяти постоянно с любым входным файлом (около 400 кб на моей машине).

Конечно, вам не нужно запускать сервер mysql, чтобы использовать replace, он только упакован таким образом в Fedora. Другие дистрибутивы /операционные системы могут поставляться отдельно.

ответил legolegs 30 SatEurope/Moscow2017-12-30T00:11:07+03:00Europe/Moscow12bEurope/MoscowSat, 30 Dec 2017 00:11:07 +0300 2017, 00:11:07
14

GNU grep может отображать смещение совпадений в двоичных файлах без необходимости считывать целые строки в память. Затем вы можете использовать dd для чтения до этого смещения, пропустить совпадение, а затем продолжить копирование из файла.

file=...
newfile=...
replace='<raw_unk>'
grep -o -b -a -F '<unk>' <"$file" |
(   pos=0
    while IFS=$IFS: read offset pattern
    do size=${#pattern}
       let skip=offset-pos
       let big=skip/1048576
       let skip=skip-big*1048576
       dd bs=1048576 count=$big <&3
       dd bs=1 count=$skip <&3
       dd bs=1 count=$size of=/dev/null <&3
       printf "%s" "$replace"
       let pos=offset+size
    done
    cat <&3
) 3<"$file" >"$newfile"

Для скорости я разделил dd на большое количество блоков размером 1048576 и меньше читал по 1 байт за раз, но эта операция будет по-прежнему немного медленной на таком большом файл. Выход grep представляет собой, например, 13977:<unk>, и это разделяется на двоеточие путем чтения в переменные offset и pattern. Мы должны отслеживать в pos количество байтов, которые уже были скопированы из файла.

ответил meuh 29 FriEurope/Moscow2017-12-29T19:37:15+03:00Europe/Moscow12bEurope/MoscowFri, 29 Dec 2017 19:37:15 +0300 2017, 19:37:15
10

Использование perl

Управление вашими буферами

Вы можете использовать IO::Handle 'setvbuf для управления буферами по умолчанию или вы можете управлять своими буферами с помощью sysread и syswrite. Для получения дополнительной информации проверьте perldoc -f sysread и perldoc -f syswrite, по сути, они пропускают буферизованный io.

Здесь мы запускаем собственный буфер IO, но делаем это вручную и произвольно на 1024 байта. Мы также открываем файл для RW, поэтому делаем все это на одном и том же FH сразу.

use strict;
use warnings;
use Fcntl qw(:flock O_RDWR);
use autodie;
use bytes;

use constant CHUNK_SIZE => 1024 * 32;

sysopen my $fh, 'file', O_RDWR;
flock($fh, LOCK_EX);

my $chunk = 1;
while ( sysread $fh, my $bytes, CHUNK_SIZE * $chunk ) {
  if ( $bytes =~ s/<unk>/<raw_unk>/g ) {
    seek( $fh, ($chunk-1)* CHUNK_SIZE, 0 );
    syswrite( $fh, $bytes, 1024);
    seek( $fh, $chunk * CHUNK_SIZE, 0 );
  }
  $chunk++;
}

Если вы собираетесь пройти этот маршрут

  1. Убедитесь, что <unk> и <raw_unk> - это тот же размер байта.
  2. Вы можете захотеть убедиться, что наш буферный метод не пересекает границу CHUNKSIZE, если вы заменяете более 1 байт.
ответил Evan Carroll 29 FriEurope/Moscow2017-12-29T23:47:20+03:00Europe/Moscow12bEurope/MoscowFri, 29 Dec 2017 23:47:20 +0300 2017, 23:47:20
10

Вот еще одна единственная командная строка UNIX, которая может работать лучше, чем другие параметры, потому что вы можете «охотиться» за «размер блока», который хорошо работает. Чтобы это было надежным, вам нужно знать, что у вас есть хотя бы одно место в каждом символе X, где X - ваш произвольный «размер блока». В приведенном ниже примере я выбрал «размер блока» из 1024 символов.

fold -w 1024 -s corpus.txt | sed 's/<unk>/<raw_unk>/g' | tr '/n' '/0'

Здесь сгиб будет до 1024 байта, но -s убедится, что он разбивается на пробел, если по крайней мере один с момента последнего разрыва.

Команда sed является вашей и делает то, что вы ожидаете.

Затем команда tr будет «разворачивать» файл, преобразующий новые строки, которые были вставлены обратно в ничто.

Вам следует подумать о том, чтобы попытаться увеличить размер блока, чтобы убедиться, что он работает быстрее. Вместо 1024 вы можете попробовать 10240 и 102400 и 1048576 для опции -w fold.

Вот пример, разбитый на каждый шаг, который преобразует все N в строчные буквы:

[[email protected] ~]# cat mailtest.txt
test XJS C4JD QADN1 NSBN3 2IDNEN GTUBE STANDARD ANTI UBE-TEST EMAIL*C.34X test

[[email protected] ~]# fold -w 20 -s mailtest.txt
test XJS C4JD QADN1
NSBN3 2IDNEN GTUBE
STANDARD ANTI
UBE-TEST
EMAIL*C.34X test

[[email protected] ~]# fold -w 20 -s mailtest.txt | sed 's/N/n/g'
test XJS C4JD QADn1
nSBn3 2IDnEn GTUBE
STAnDARD AnTI
UBE-TEST
EMAIL*C.34X test

[[email protected] ~]# fold -w 20 -s mailtest.txt | sed 's/N/n/g' | tr '\n' '\0'
test XJS C4JD QADn1 nSBn3 2IDnEn GTUBE STAnDARD AnTI UBE-TEST EMAIL*C.34X test

Вам нужно будет добавить новую строку в самый конец файла, если она есть, потому что команда tr удалит ее.

ответил alfreema 30 SatEurope/Moscow2017-12-30T19:30:13+03:00Europe/Moscow12bEurope/MoscowSat, 30 Dec 2017 19:30:13 +0300 2017, 19:30:13
8

Вы можете попробовать bbe ( редактор двоичных блоков ), "sed для двоичных файлов".

У меня был хороший успех, используя его в текстовом файле 7 ГБ без символов EOL, заменяя несколько вхождений строки одной из разной длины. Без попытки оптимизации он дал среднюю производительность обработки> 50Мб /с.

ответил ovirt 31 SunEurope/Moscow2017-12-31T05:52:00+03:00Europe/Moscow12bEurope/MoscowSun, 31 Dec 2017 05:52:00 +0300 2017, 05:52:00
5

С perl вы можете работать с фиксированными записями длины, например:

perl -pe 'BEGIN{$/=\1e8}
          s/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new

И надеюсь, что не будет <unk> s, охватывающих две из этих записей 100 МБ.

ответил Stéphane Chazelas 30 SatEurope/Moscow2017-12-30T00:07:13+03:00Europe/Moscow12bEurope/MoscowSat, 30 Dec 2017 00:07:13 +0300 2017, 00:07:13
5

Вот небольшая программа Go, которая выполняет задачу (unk.go):

 package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
)

func main() {
    const (
        pattern     = "<unk>"
        replacement = "<raw_unk>"
    )
    var match int
    var char rune
    scanner := bufio.NewScanner(os.Stdin)
    scanner.Split(bufio.ScanRunes)
    for scanner.Scan() {
        char = rune(scanner.Text()[0])
        if char == []rune(pattern)[match] {
            match++
            if match == len(pattern) {
                fmt.Print(replacement)
                match = 0
            }
        } else {
            if match > 0 {
                fmt.Print(string(pattern[:match]))
                match = 0
            }
            if char == rune(pattern[0]) {
                match = 1
            } else {
                fmt.Print(string(char))
            }
        }
    }
    if err := scanner.Err(); err != nil {
        log.Fatal(err)
    }
}

Просто создайте его с помощью go build unk.go и запустите его как ./unk <input >output.

EDIT:

Извините, я не читал, что все в одной строке, поэтому я попытался прочитать символ файла по символу.

EDIT II:

Применяется такое же исправление, что и в программе C.

ответил Patrick Bucher 29 FriEurope/Moscow2017-12-29T18:58:49+03:00Europe/Moscow12bEurope/MoscowFri, 29 Dec 2017 18:58:49 +0300 2017, 18:58:49
1

Это может быть излишним для файла на 70 Гбайт и простого поиска & замените, но инфраструктура Hadoop MapReduce решит вашу проблему прямо сейчас без каких-либо затрат (выберите опцию «Один узел» при ее настройке для ее локального запуска) - и в будущем ее можно будет масштабировать до бесконечной емкости без необходимости изменения ваш код.

Официальный учебник в https://hadoop.apache.org/docs/stable/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html использует (чрезвычайно простую) Java, но вы можете найти клиентские библиотеки для Perl или любой другой язык, который вы хотите использовать.

Итак, если позже вы обнаружите, что выполняете более сложные операции с текстовыми файлами 7000GB - и это нужно делать 100 раз в день, вы можете распределить рабочую нагрузку на нескольких узлах, которые вы предоставляете или которые автоматически предоставляются для вас облачный кластер Hadoop.

ответил Sam Rahimi 4 Jpm1000000pmThu, 04 Jan 2018 20:25:01 +030018 2018, 20:25:01
-1

Если у нас есть минимальное количество <unk> (как и ожидалось по закону Zipf),

awk -v RS="<unk>" -v ORS="<raw_unk>" 1
ответил JJoao 16 MarpmFri, 16 Mar 2018 12:30:08 +03002018-03-16T12:30:08+03:0012 2018, 12:30:08

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132