Как эффективно разделить большой текстовый файл без разделения многострочных записей?

попробовать kwin --replace или DISPLAY=:0 kwin --replace если Вы не находитесь в X.
Источник

9
13.04.2017, 15:36
7 ответов

Я не думаю, что вы сможете это сделать - ненадежно и не так, как вы просите. Дело в том, что степень сжатия архива, вероятно, не будет равномерно распределена от головы к хвосту - алгоритм сжатия будет лучше применяться к одним частям, чем к другим. Вот как это работает. И поэтому вы не можете влиять на размер сжатого файла.

Более того, gzip просто не поддерживает сохранение исходного размера сжатых файлов размером более 4 ГБ - он не может с этим справиться. И поэтому вы не можете запросить архив, чтобы получить надежный размер - это вас обманет.

4 строчки - это действительно очень просто. 4-файловая штука - я просто не знаю, как вы могли бы сделать это надежно и с равномерным распределением без предварительного извлечения архива, чтобы получить его несжатый размер. Я не думаю, что ты сможешь, потому что я пытался.

Однако то, что вы можете сделать, - это установить максимальный размер для разделенных выходных файлов и убедиться, что они всегда не работают на рекордных барьерах. Это легко сделать. Вот небольшой скрипт, который сделает это путем извлечения архива gzip и передачи содержимого через несколько явных dd буферов конвейера с определенным count = $ rpt аргументы, прежде чем передавать это через lz4 для распаковки / повторного сжатия каждого файла на лету. Я также добавил несколько небольших уловок tee pipe, чтобы вывести последние четыре строки для каждого сегмента в stderr.

(       IFS= n= c=$(((m=(k=1024)*k)/354))
        b=bs=354xk bs=bs=64k
        pigz -d </tmp/gz | dd i$bs o$b |
        while   read -r line _$((n+=1))
        do      printf \\n/tmp/lz4.$n\\n
        { {     printf %s\\n "$line"
                dd count=$c i$b o$bs
        }|      tee /dev/fd/3|lz4 -BD -9 >/tmp/lz4.$n
        } 3>&1| tail -n4 |tee /dev/fd/2 |
                wc -c;ls -lh /tmp/[gl]z*
        done
)

Это будет продолжаться до тех пор, пока не будет обработан весь ввод.Он не пытается разделить его на какой-то процент, чего он не может получить, а вместо этого разбивает его по максимальному количеству необработанных байтов на разделение. И в любом случае большая часть вашей проблемы заключается в том, что вы не можете получить надежный размер вашего архива, потому что он слишком большой - что бы вы ни делали, не делайте этого снова - делайте разбиения менее 4 ГБ на кусок в этом цикле , может быть. По крайней мере, этот небольшой скрипт позволяет вам делать это, даже не записывая несжатый байт на диск.

Вот более короткая версия, урезанная до самого важного - она ​​не добавляет всего материала к отчету:

(       IFS= n= c=$((1024*1024/354))
        pigz -d | dd ibs=64k obs=354xk |
        while   read -r line _$((n+=1))
        do {    printf %s\\n "$line"
                dd count=$c obs=64k ibs=354xk
        }  |    lz4 -BD -9  >/tmp/lz4.$n
        done
)  </tmp/gz

Она делает все то же, что и первая, в основном, ей просто не так много сказать об этом. Кроме того, здесь меньше беспорядка, поэтому, возможно, легче увидеть, что происходит.

Параметр IFS = предназначен только для обработки одной строки чтения за итерацию. Мы читаем один, потому что нам нужно, чтобы наш цикл заканчивался, когда заканчивается ввод. Это зависит от вашей записи - размера - который, согласно вашему примеру, составляет 354 байта на. Я создал архив размером 4+ ГБ gzip с некоторыми случайными данными, чтобы проверить его.

Случайные данные были получены следующим образом:

(       mkfifo /tmp/q; q="$(echo '[1+dPd126!<c]sc33lcx'|dc)"
        (tr '\0-\33\177-\377' "$q$q"|fold -b144 >/tmp/q)&
        tr '\0-\377' '[A*60][C*60][G*60][N*16][T*]' | fold -b144 |
        sed 'h;s/^\(.\{50\}\)\(.\{8\}\)/@N\1+\2\n/;P;s/.*/+/;H;x'|
        paste "-d\n" - - - /tmp/q| dd bs=4k count=kx2k  | gzip
)       </dev/urandom >/tmp/gz 2>/dev/null

... но, возможно, вам не стоит так сильно об этом беспокоиться, поскольку у вас уже есть данные и все такое. Вернемся к решению ...

В основном pigz - который, кажется, распаковывает немного быстрее, чем zcat - выводит несжатый поток, и dd буферы, которые выводятся в блоки записи, размер которых определенно кратен 354 байтам.Цикл будет читать $ line один раз на каждой итерации, чтобы проверить, что ввод все еще поступает, после чего он printf будет printf в lz4 перед вызовом другого dd для чтения блоков, размер которых определенно кратен 354 байтам - для синхронизации с процессом буферизации dd на время. Будет одно короткое чтение на итерацию из-за начальной read $ line - но это не имеет значения, потому что мы печатаем это в lz4 - в нашем процессе сборщика - в любом случае.

Я настроил его таким образом, чтобы каждая итерация считывала примерно 1 ГБ несжатых данных и сжимала их в потоке примерно до 650 МБ или около того. lz4 намного быстрее, чем любой другой полезный метод сжатия - поэтому я выбрал его здесь, потому что не люблю ждать. xz , вероятно, гораздо лучше справится с фактическим сжатием. Однако одна особенность lz4 заключается в том, что он часто может распаковываться со скоростью, близкой к скорости ОЗУ, что означает, что во многих случаях вы можете распаковать архив lz4 так же быстро, как и все равно запиши это в память.

Большой делает несколько отчетов за итерацию. Оба цикла будут печатать отчет dd о количестве переданных необработанных байтов, скорости и так далее. Большой цикл также будет печатать последние 4 строки ввода за цикл и счетчик байтов для них, за которым следует ls каталога, в который я записываю архивы lz4 .Вот несколько этапов вывода:

/tmp/lz4.1
2961+1 records in
16383+1 records out
1073713090 bytes (1.1 GB) copied, 169.838 s, 6.3 MB/s
@NTACGTANTTCATTGGNATGACGCGCGTTTATGNGAGGGCGTCCGGAANGC+TCTCTNCC
TACGTANTTCATTGGNATGACGCGCGTTTATGNGAGGGCGTCCGGAANGCTCTCTNCCGAGCTCAGTATGTTNNAAGTCCTGANGNGTNGCGCCTACCCGACCACAACCTCTACTCGGTTCCGCATGCATGCAACACATCGTCA
+
I`AgZgW*,`Gw=KKOU:W5dE1m=-"9W@[AG8;<P7P6,qxE!7P4##,Q@c7<nLmK_u+IL4Kz.Rl*+w^A5xHK?m_JBBhqaLK_,o;p,;QeEjb|">Spg`MO6M'wod?z9m.yLgj4kvR~+0:.X#(Bf
354

-rw-r--r-- 1 mikeserv mikeserv 4.7G Jun 16 08:58 /tmp/gz
-rw-r--r-- 1 mikeserv mikeserv 652M Jun 16 12:32 /tmp/lz4.1

/tmp/lz4.2
2961+1 records in
16383+1 records out
1073713090 bytes (1.1 GB) copied, 169.38 s, 6.3 MB/s
@NTTGTTGCCCTAACCANTCCTTGGGAACGCAATGGTGTGANCTGCCGGGAC+CTTTTGCT
TTGTTGCCCTAACCANTCCTTGGGAACGCAATGGTGTGANCTGCCGGGACCTTTTGCTGCCCTGGTACTTTTGTCTGACTGGGGGTGCCACTTGCAGNAGTAAAAGCNAGCTGGTTCAACNAATAAGGACNANTTNCACTGAAC
+
>G-{N~Q5Z5QwV??I^~?rT+S0$7Pw2y9MV^BBTBK%HK87(fz)HU/0^%JGk<<1--7+r3e%X6{c#w@aA6Q^DrdVI0^8+m92vc>RKgnUnMDcU:j!x6u^g<Go?p(HKG@$4"T8BWZ<z.Xi
354

-rw-r--r-- 1 mikeserv mikeserv 4.7G Jun 16 08:58 /tmp/gz
-rw-r--r-- 1 mikeserv mikeserv 652M Jun 16 12:32 /tmp/lz4.1
-rw-r--r-- 1 mikeserv mikeserv 652M Jun 16 12:35 /tmp/lz4.2
4
27.01.2020, 20:05

Это не должно быть серьезным ответом! Я просто играл с flex и это, скорее всего, не сработает на входном файле с ~50Гб (если вообще с большими входными данными, чем в моем тестовом файле):

Это работает на ~1Гб файле input.txt:

Учитывая flex входной файл разветвителя. l:

%{
#include <stdio.h>
extern FILE* yyin;
extern FILE* yyout;

int input_size = 0;

int part_num;
int part_num_max;
char **part_names;
%}

%%
@.+ {
        if (ftell(yyout) >= input_size / part_num_max) {
            fclose(yyout);
            if ((yyout = fopen(part_names[++part_num], "w")) == 0) {
                exit(1);
            }
        }
        fprintf(yyout, "%s", yytext);
    }
%%

int main(int argc, char *argv[]) {

    if (argc < 2) {
        return 1;
    } else if ((yyin = fopen(argv[1], "r")) == 0) {
        return 1;
    } else if ((yyout = fopen(argv[2], "w")) == 0) {
        fclose(yyin);
        return 1;
    } else {

        fseek(yyin, 0L, SEEK_END);
        input_size = ftell(yyin);
        rewind(yyin);

        part_num = 0;
        part_num_max = argc - 2;
        part_names = argv + 2;

        yylex();

        fclose(yyin);
        fclose(yyout);
        return 0;
    }
}

генерирует lex.yy.c и компилирует его в двоичный файл сплиттера с:

$ flex splitter.l && gcc lex.yy.c -ll -o splitter

использованием:

$ ./splitter input.txt output.part1 output.part2 output.part3 output.part4

Время работы для 1 Гб входа. txt:

$ time ./splitter input.txt output.part1 output.part2 output.part3 output.part4

real    2m43.640s
user    0m48.100s
sys     0m1.084s
2
27.01.2020, 20:05

Из того, что я понял после проверки Google-сферы и дальнейшего тестирования файла 7,8 ГиБ .gz , кажется, что метаданные размера исходного несжатого файла неточны (т. Е. неверно ) для больших файлов .gz (больше 4 ГБ (возможно, 2 ГБ для некоторых версий gzip ).
Re. my test of gzip's метаданные:

* The compressed.gz file is  7.8 GiB ( 8353115038 bytes) 
* The uncompressed  file is 18.1 GiB (19436487168 bytes)
* The metadata says file is  2.1 GiB ( 2256623616 bytes) uncompressed

Таким образом, кажется, что невозможно определить размер несжатого файла, не распаковав его (что, мягко говоря, немного грубо!)

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

Он использует размер файла в байтах (через stat ), а с awk подсчет байтов ( не символы). Независимо от того, является ли конец строки LF | CR | CRLF , этот скрипт обрабатывает длину конца строки с помощью встроенной переменной RT ).

LC_ALL=C gawk 'BEGIN{"stat -c %s "ARGV[1] | getline inSize
                      segSiz=int(inSize/4)+((inSize%4)==0?0:1)
                      ouSplit=segSiz; segNb=0 }
               { lnb++; bytCt+=(length+length(RT))
                 print $0 > ARGV[1]"."segNb
                 if( lnb!=4 ) next
                 lnb=0
                 if( bytCt>=ouSplit ){ segNb++; ouSplit+=segSiz }
               }' myfile

Ниже приведен тест, который я использовал для проверки того, что количество строк в каждом файле равно mod 4 == 0

for i in myfile  myfile.{0..3}; do
    lc=$(<"$i" wc -l)
    printf '%s\t%s\t' "$i" $lc; 
    (( $(echo $lc"%4" | bc) )) && echo "Error: mod 4 remainder !" || echo 'mod 4 ok'  
done | column -ts$'\t' ;echo

Результат теста:

myfile    1827904  mod 4 ok
myfile.0  456976   mod 4 ok
myfile.1  456976   mod 4 ok
myfile.2  456976   mod 4 ok
myfile.3  456976   mod 4 ok

myfile был сгенерирован:

printf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4} > myfile
3
27.01.2020, 20:05

Вот решение на Python, которое заставляет один раз пройтись по входному файлу, записывая выходные файлы по ходу работы.

Особенностью использования wc -l является то, что вы предполагаете, что каждая из записей здесь имеет одинаковый размер. Здесь это может быть правдой, но решение, приведенное ниже, работает даже тогда, когда это не так. В основном это использование wc -c или количество байт в файле. На Python это делается с помощью os.stat()

Так что вот как работает программа. Сначала мы вычисляем идеальные точки разделения как смещения байтов. Затем читаем строки входного файла, записывая их в соответствующий выходной файл. Когда вы видите, что превысили оптимальную следующую точку разделения и , вы находитесь на границе записи, закрываете последний выходной файл и открываете следующий.

В этом смысле программа оптимальна, она считывает байты входного файла один раз; получение размера файла не требует чтения данных файла. Необходимое количество памяти пропорционально размеру строки. Но Python или система предположительно имеют разумные файловые буферы для ускорения ввода/вывода.

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

И очевидно, что это может быть переведено и на другие языки программирования.

Другое дело, я не уверен, что Windows с его crlf правильно обрабатывает длину строки, как это делается на Unix-y системах. Если len() здесь выключена на один, то я надеюсь, что это очевидно, как настроить программу.

#!/usr/bin/env python
import os

# Adjust these
filename = 'file.txt'
rec_size = 4
file_splits = 4

size = os.stat(filename).st_size
splits = [(i+1)*size/file_splits for i in range(file_splits)]
with open(filename, 'r') as fd:
    linecount = 0
    i = 0 # File split number
    out = open('file%d.txt' % i, 'w')
    offset = 0  # byte offset of where we are in the file: 0..size
    r = 0 # where we are in the record: 0..rec_size-1
    for line in fd:
        linecount += 1
        r = (r+1) % rec_size
        if offset + len(line) > splits[i] and r == 1 :
            out.close()
            i += 1
            out = open('file%d.txt' % i, 'w')
        out.write(line)
        offset += len(line)
    out.close()
    print("file %s has %d lines" % (filename, linecount))
1
27.01.2020, 20:05

Символьные файлы ссылок занимают больше места. Для жестко связанных файлов используется один и тот же inode ; но символьный файл - это указатель на оригинал ( расположение ).

Несмотря на это, существуют два исключения для жестких ссылок:

  • Не все файловые системы поддерживают жесткие ссылки.
  • Жесткие ссылки не могут применяться к папкам.

Думаю, вам не нужно рассматривать проблему места хранения, так как в большинстве случаев они тривиальны. Кроме того, могут существовать некоторые инструменты, помогающие организовать жанры виртуально (они могут использовать преимущества виртуальных файловых систем).

-121--72408-

Обычно нет - один раз на машине (виртуальная или нет) подключается к VPN, он ведет себя так, как будто физически подключен к этой VPN, он больше не доступен из и больше не может получать доступ к другим машинам в локальной сети (если только не возможны соединения между локальными сетевыми машинами и любой другой машиной внутри VPN, что, в свою очередь, нарушает целевое назначение VPN).

В зависимости от реализации VPN, она может быть технически разрешена посредством конфигурирования со стороны сервера VPN, ищите раздельное туннелирование VPN. Я сомневаюсь, что корпоративная ИТ-служба разрешит это, поскольку она считается дырой в безопасности.

-121--89214-

Пользователь FloСебя казался любопытным в отношении решения TXR . Здесь используется встроенный TXR Lisp :

(defvar splits 4)
(defvar name "data")

(let* ((fi (open-file name "r"))                 ;; input stream
       (rc (tuples 4 (get-lines fi)))            ;; lazy list of 4-tuples
       (sz (/ (prop (stat name) :size) splits))  ;; split size
       (i 1)                                     ;; split enumerator
       (n 0)                                     ;; tuplecounter within split
       (no `@name.@i`)                           ;; output split file name
       (fo (open-file no "w")))                  ;; output stream
  (whilet ((r (pop rc)))  ;; pop each 4-tuple
    (put-lines r fo) ;; send 4-tuple into output file
    ;; if not on the last split, every 1000 tuples, check the output file
    ;; size with stat and switch to next split if necessary.
    (when (and (< i splits)
               (> (inc n) 1000)
               (>= (seek-stream fo 0 :from-current) sz))
      (close-stream fo)
      (set fo (open-file (set no `@name.@(inc i)`) "w")
           n 0)))
  (close-stream fo))

Примечания:

  1. По той же причине pop -пинг каждого кортежа из ленивого списка кортежей важен, так что ленивый список потребляется. Мы не должны сохранять ссылку на начало этого списка, потому что тогда память будет расти по мере прохождения через файл.

  2. (seek-stream f0: from-current) - это no-op случай seek-stream , который делает себя полезным, возвращая текущее положение.

  3. Производительность: не упоминайте об этом. Полезно, но не будет приносить трофеи домой.

  4. Поскольку мы проверяем размер только каждые 1000 кортежей, мы можем просто сделать размер кортежа 4000 линий.

1
27.01.2020, 20:05

Разделение файлов по границам записи на самом деле очень просто, без какого-либо кода:

zcat your_file.gz | split -l 10000 - output_name_

Это создаст выходные файлы по 10000 строк каждый с именами output_name_aa, output_name_ab, output_name_ac, ... При таком большом вводе, как ваш, это даст вам много выходных файлов. Замените 10000 любым числом, кратным четырем, и вы сможете делать выходные файлы сколь угодно большими или маленькими. К сожалению, как и в случае с другими ответами, нет хорошего способа гарантировать, что вы получите желаемое количество (приблизительно) равного размера выходных файлов, не делая некоторых предположений о вводе. (Или, на самом деле, пропустить все это через wc .) Если ваши записи примерно одинакового размера (или, по крайней мере, примерно равномерно распределены), вы можете попробовать дать оценку, подобную этой:

zcat your_file.gz | head -n4000 | gzip | wc -c

Это покажет у вас сжатый размер первых 1000 записей вашего файла. Исходя из этого, вы, вероятно, сможете оценить, сколько строк вы хотите в каждом файле, чтобы в итоге получилось четыре файла.(Если вы не хотите, чтобы оставался вырожденный пятый файл, обязательно немного увеличьте свою оценку или будьте готовы прикрепить пятый файл к хвосту четвертого.)

Изменить: вот еще один трюк , предполагая, что вам нужны сжатые выходные файлы:

#!/bin/sh

base=$(basename $1 .gz)
unpigz -c $1 | split -l 100000 --filter='pigz -c > _$FILE.gz' - ${base}_

batch=$((`ls _*.gz | wc -l` / 4 + 1))
for i in `seq 1 4`; do
  files=`ls _*.gz | head -$batch`
  cat $files > ${base}_$i.gz && rm $files
done

Это создаст много файлов меньшего размера, а затем быстро объединит их вместе. (Возможно, вам придется настроить параметр -l в зависимости от длины строк в ваших файлах.) Предполагается, что у вас есть относительно последняя версия GNU coreutils (для split --filter) и около 130% размера вашего входного файла в свободное место на диске. Замените gzip / zcat на pigz / unpigz, если у вас их нет. Я слышал, что некоторые программные библиотеки (Java?) Не могут обрабатывать файлы gzip, объединенные таким образом, но пока у меня не было с этим проблем. (pigz использует тот же прием для распараллеливания сжатия.)

4
27.01.2020, 20:05

Если вам не нужно, чтобы новые файлы были смежными частями оригинального файла, вы можете сделать это полностью с помощью sed следующим образом:

sed -n -e '1~16,+3w1.txt' -e '5~16,+3w2.txt' -e '9~16,+3w3.txt' -e '13~16,+3w4.txt'

-n останавливает его от печати каждой строки, и каждый из сценариев -e по сути делает то же самое. 1~16 соответствует первой строке, а каждая 16-я строка после. ,+3 означает, что следующие три строки после каждой из них совпадают. w1.txt говорит, что все эти строки нужно записать в файл 1.txt. Это берет каждую 4-ю группу из 4-х строк и записывает ее в файл, начиная с первой группы из 4-х строк. Остальные три команды делают то же самое, но каждая из них сдвигается вперед на 4 строки, и записывают в другой файл.

Это ужасно сломается, если файл не будет точно соответствовать спецификации, которую вы изложили, но в противном случае он должен работать так, как вы задумывали. Я его не профилировал, поэтому не знаю, насколько он будет эффективен, но sed достаточно эффективен при потоковом редактировании.

0
27.01.2020, 20:05

Теги

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