Як ефективно розділити великий текстовий файл без розділення багаторядкових записів?


9

У мене великий текстовий файл (~ 50Gb, коли gz'ed). Файл містить 4*Nрядки або Nзаписи; тобто кожен запис складається з 4 рядків. Я хотів би розділити цей файл на 4 менші файли розміром приблизно 25% від вхідного файлу. Як я можу розділити файл на межі запису?

Наївним підходом було б zcat file | wc -lотримати підрахунок рядків, розділити це число на 4 і потім використовувати split -l <number> file. Однак це переходить у файл двічі, а лічильник ліній вкрай повільний (36 хвилин). Чи є кращий спосіб?

Це близько, але я не те, що я шукаю. Прийнята відповідь також робить кількість рядків.

Редагувати:

Файл містить дані про послідовність у форматі fastq. Два записи виглядають так (анонімізовано):

@NxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxGCGA+ATAGAGAG
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTTTATGTTTTTAATTAATTCTGTTTCCTCAGATTGATGATGAAGTTxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
AAAAA#FFFFFFFFFFFFAFFFFF#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF<AFFFFFFFFFFAFFFFFFFFFFFFFFFFFFF<FFFFFFFFFAFFFAFFAFFAFFFFFFFFAFFFFFFAAFFF<FAFAFFFFA
@NxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxGCGA+ATAGAGAG
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCCCTCTGCTGGAACTGACACGCAGACATTCAGCGGCTCCGCCGCCxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
AAAAA#FFFFF7FFFFFFAFFFFA#F7FFFFFFFFF7FFFFFAF<FFFFFFFFFFFFFFAFFF.F.FFFFF.FAFFF.FFFFFFFFFFFFFF.)F.FFA))FFF7)F7F<.FFFF.FFF7FF<.FFA<7FA.<.7FF.FFFAFF

Перший рядок кожного запису починається з а @.

EDIT2:

zcat file > /dev/null займає 31 хв.

EDIT3: Тільки перший рядок починається з @. Ніхто з інших ніколи не буде. Дивіться тут . Записи повинні залишатися в порядку. Не нормально нічого додати в отриманий файл.


Скільки часу займає одиночний zcat file > /dev/null?
choroba

Чи можете ви надати невеликий зразок відповідного файлу?
FloHimself

Ви кажете, що кожен запис починається з того, @що також є 4 рядки на запис. Чи обоє ці абсолюти? - і чи можна починати рядки 2,3,4 @? і чи є у файлі якісь незаписані заголовки ліній колонтитулів?
Пітер.O

1
Ви шукаєте рішення, яке обробляє стиснений вхід та / або створює стислий вихід? Шукаєте чотири стислих файли однакового розміру?
Стівен Кітт

Відповіді:


4

Я не думаю, що ти можеш це зробити - не надійно, і не так, як ти просиш. Справа в тому, що коефіцієнт стиснення архіву, ймовірно, не буде рівномірно розподілений від голови до хвоста - алгоритм стиснення буде застосовуватися краще до деяких частин, ніж до інших. Ось так воно і працює. Таким чином, ви не можете розподілити коефіцієнт поділу на розмір стисненого файлу.

Більше того, gzipпросто не підтримує збереження оригінального розміру стислих файлів розміром більше 4 Гб - він не може впоратися з цим. І тому ви не можете запитувати архів, щоб отримати надійний розмір - адже він вас обдурить.

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

Однак, що ви можете зробити, встановіть максимальний розмір для розділених вихідних файлів і переконайтеся, що вони завжди порушені на межі запису. Це ви легко можете зробити. Ось невеликий сценарій, який зробить це, витягнувши gzipархів і прокачивши вміст через декілька явних ddбуферних буферів з конкретними count=$rptаргументами, перед тим як пропустити його lz4для декомпресії / повторного повторного копіювання кожного файлу на льоту. Я також наклав кілька невеликих teeтрюків, щоб надрукувати останні чотири рядки для кожного сегмента, щоб також було складніше.

(       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=справа в тому, щоб обробити один readрядок за ітерацію. Ми readодин, тому що нам потрібно, щоб наш цикл закінчився, коли вхід закінчується. Це залежить від вашого розміру запису - який, на ваш приклад, становить 354 байти на кожен. Я створив gzipархів 4 + gb з деякими випадковими даними, щоб перевірити його.

Випадкові дані отримані таким чином:

(       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 байти. Цикл буде один раз в кожній ітерації тесту , що введення ще прибуває, який він буде потім на перед іншим , називається для читання розмірів блоків конкретно на кратна 354-байт - для синхронізації з буферним процесу - в протягом всього терміну. Через первинне буде одне коротке читання за кожну ітерацію - але це не має значення, тому що ми це все-таки друкуємо в процесі колекціонування.read$lineprintfprintflz4ddddread $linelz4

Я встановив це так, що кожна ітерація буде читати приблизно 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

gzip -lпрацює лише для <2GiB нестиснених файлів IIRC (щось менше, ніж файл OP).
Стефан Шазелас

@ StéphaneChazelas - чорт. Це єдиний спосіб, коли я міг би зрозуміти отримання нестисненого розміру. Без цього це зовсім не працює.
mikeserv

4

Розщеплення файлів на межі запису насправді дуже легко, без будь-якого коду:

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 використовує той же трюк, щоб паралелізувати стиснення.)


Якщо у вас встановлено pigz, ви можете прискорити роботу, замінивши "pigz -cd" на "zcat".
Дрю

2
Ага, я щойно помітив, що ви вже згадали про розкол у питанні. Але насправді, майже будь-яке рішення буде робити те саме, що розділити під капотом. Важка частина полягає у з'ясуванні, скільки рядків потрібно помістити в кожен файл.
Дрю

3

З того, що я збираю після перевірки google-сфери та подальшого тестування .gzфайлу 7,8 GiB , видається, що метадані оригінального розміру нестисненого файлу не є точними (тобто неправильними ) для великих .gzфайлів (більше 4GiB (можливо, для деяких 2GiB) версії gzip).
Re мій тест метаданих GZIP в .:

* 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

2

Це не означає серйозної відповіді! Я просто грав з flexцим, і, швидше за все, це не працюватиме на вхідному файлі з ~ 50Gb (якщо взагалі, на більших вхідних даних, ніж мій тестовий файл):

Це працює для мене на файлі ~ 1Gb input.txt :

З огляду на flexвхідний файл splitter.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 та компіляція його у splitterдвійкове з:

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

Використання:

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

Час роботи для 1Gb input.txt :

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

real    2m43.640s
user    0m48.100s
sys     0m1.084s

Фактична лексика тут настільки проста, що від lex ви не отримаєте користі. Просто зателефонуйте getc(stream)та застосуйте просту логіку. Крім того, чи знаєте ви, що (крапка) символ регулярного виразів у (f) lex відповідає будь-якому символу, окрім нового рядка , правда? В той час як ці записи є багаторядковими.
Каз

@Kaz Хоча ваші твердження, як правило, є актуальними, це фактично працює з даними, наданими в Q.
FloHimself

Лише випадково, тому що існує правило за замовчуванням, коли нічого не відповідає: споживайте символ та друкуйте його до виводу! В інших словах ви можете переключити файли просто за допомогою правила, яке розпізнає @символ, а потім дозволити правилу за замовчуванням копіювати дані. Тепер у вас є правило, яке копіює частину даних як один великий маркер, а потім правило за замовчуванням отримує другий рядок по одному символу.
Каз

Дякуємо за уточнення. Цікаво, як би ти вирішив це завдання txr.
FloHimself

Я не впевнений, що це зробив би, бо завдання полягає в тому, щоб зробити дуже просту річ з великою кількістю даних, якнайшвидше.
Каз

1

Ось рішення в 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))

Це не розщеплення на межі запису. напр. Перший розділ підрозділу відбувається після 3-го рядка з цим входомprintf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4}
Peter.O

1

Користувач FloHimself видався цікавим щодо рішення 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 fo 0 :from-current)випадок без вибору seek-stream, який робить себе корисним, повертаючи поточну позицію.

  3. Продуктивність: не згадуйте про це. Корисні, але трофеї не будуть приносити додому.

  4. Оскільки ми проводимо перевірку розміру кожні 1000 кортежів, ми можемо просто зробити кортеж розміром 4000 ліній.


0

Якщо вам не потрібно, щоб нові файли були суміжними фрагментами оригінального файлу, ви можете зробити це повністю за 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 рядки і записують у інший файл.

Це жахливо зламається, якщо файл не точно відповідає специфікації, яку ви виклали, але в іншому випадку він повинен працювати так, як ви задумали. Я не профілював це, тому не знаю, наскільки він буде ефективним, але sedє досить ефективним при редагуванні потоків.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.