Как удалить первые 300 миллионов строк из txt-файла размером 700 ГБ в системе с 1 ТБ дискового пространства?

Поскольку оболочка gnome -предоставляет интерфейс JS eval на DBus, который имеет доступ ко всем переменным, это возможно с помощью следующей команды:

gdbus call --session --dest org.gnome.Shell \
--object-path /org/gnome/Shell \
--method org.gnome.Shell.Eval \
 "imports.ui.status.keyboard.getInputSourceManager().inputSources[0].activate()"

Что активирует 0-й макет и т.д. Тривиально назначить эти команды, скажем, малоиспользуемым 無変換 и 変換 на японской клавиатуре.

Кредит.


А вот как переключиться на последний использованный метод ввода (из комментариев):

gdbus call --session --dest org.gnome.Shell --object-path /org/gnome/Shell \
--method org.gnome.Shell.Eval "imports.ui.status.keyboard.getInputSourceManager()._mruSources[1].activate()"

156
30.09.2020, 13:13
8 ответов

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

gzip file && zcat file.gz | tail -n +300000001 | gzip > newFile.gz

Сначала будет gzipисходный входной файл(file)для создания file.gz. Затем вы zcatтолько что созданный file.gz, передаете его через tail -n +300000001, чтобы удалить первые 3M строк, сжимаете результат для экономии места на диске и сохраняете его как newFile.gz. &&гарантирует, что вы продолжите только в том случае, если gzipоперация прошла успешно (она завершится ошибкой, если у вас закончится место ).

Обратите внимание, что текстовые файлы легко сжимаются. Например, я создал тестовый файл, используя seq 400000000 > file, который печатает числа от 1 до 400 000 000, и в результате получился файл 3,7G. Когда я сжал его с помощью приведенных выше команд, сжатый файл был всего 849 МБ, а newFile.gzя создал только 213 МБ.

133
18.03.2021, 23:03

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

Пример:Создайте образец файла с 1000 строками:

$ seq 1 1000 > 1000lines.txt
$ head -n 3 1000lines.txt
1
2
3
$ tail -n 3 1000lines.txt
998
999
1000

Мы хотим удалить первые 300 строк. Скольким байтам он соответствует?

$ stat -c %s 1000lines.txt
3893 # total bytes
$ head -n 300 1000lines.txt | wc -c
1092 # first 300 lines bytes
$ echo $((3893-1092))
2801 # target filesize after removal

Файл имеет размер 3893 байта, мы хотим удалить первые 1092 байта, оставив нам новый файл размером 2801 байт.

Чтобы удалить эти байты, мы используем команду GNU ddс conv=notrunc, иначе файл будет удален до того, как вы сможете скопировать его содержимое:

$ dd conv=notrunc iflag=skip_bytes skip=1092 if=1000lines.txt of=1000lines.txt
5+1 records in
5+1 records out
2801 bytes (2.8 kB, 2.7 KiB) copied, 8.6078e-05 s, 32.5 MB/s

Это удаляет первые 300 строк, но теперь повторяются последние 1092 байта, потому что файл еще не усечен:

$ truncate -s 2801 1000lines.txt

Это уменьшает файл до его окончательного размера, удаляя повторяющиеся строки в конце файла.

Результат:

$ stat -c %s 1000lines.txt 
2801

$ head -n 3 1000lines.txt
301
302
303

$ tail -n 3 1000lines.txt
998
999
1000

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

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

158
18.03.2021, 23:03

В некоторых файловых системах, таких как ext4 или xfs, для этого можно использовать системный вызов fallocate().

38
18.03.2021, 23:03

Я создал инструмент, который может вам пригодиться.:hexpeek — шестнадцатеричный редактор, предназначенный для работы с огромными файлами и работающий на любой последней системе POSIX -, например (, протестированной на Debian, CentOS. и FreeBSD ).

Можно использовать heexpeek или внешний инструмент, чтобы найти 300 -миллионную новую строку. Затем, предполагая, что X является шестнадцатеричным нулем -индексированной позиции первого октета после 300 -миллионной новой строки, файл можно открыть в шестнадцатеричном формате и одной командой 0, X k удалит первые X октеты в файле.

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

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

3
18.03.2021, 23:03

Еще один голос за пользовательскую программу, если вам действительно нужна эта задача. Подойдет C или любой достаточно мощный динамический язык, такой как Perl или Python. Я не буду приводить сюда источник, но опишу алгоритм, который предотвратит потерю данных при перемещении данных:

  1. Прочитайте большой файл с конца -разрыва строки счета. Собрав предварительно -определенное количество строк, которые можно безопасно разместить на свободном месте, запишите этот фрагмент в отдельный файл и обрежьте хвост большого файла. Используйте имя файла чанка для хранения номеров строк.
  2. После этого вы получите полностью стертый большой файл и множество гораздо меньших файлов, занимающих то же место.
  3. Подсчитайте свои 300 миллионов строк. -Вы можете сразу удалить все куски, соответствующие ненужным строкам, так как вы знаете, какие куски содержат какие строки.
  4. Если вам на самом деле не нужен большой файл, вы можете просто работать непосредственно с оставшимися фрагментами с помощью любых инструментов, которые вам нужны, используя подстановочные знаки или связывая их вместе с помощью catпо мере необходимости.
  5. Если вам все-таки нужен большой файл и освободившегося места достаточно для хранения суммы оставшихся кусков после того, как вы удалили ненужные, -просто объедините их вместе с cpили cat.
  6. Если вам нужен большой файл, а места недостаточно, напишите еще одну маленькую программу, которая будет выполнять действие, обратное шагу 1. :Сохраните список и индивидуальную длину каждого фрагмента в какой-нибудь файл списка. Чтение фрагментов один -за -один и добавление их во вновь созданный "большой файл". Каждый раз, когда вы добавляете фрагмент к большому файлу, вы будете удалять отдельный небольшой файл, содержащий этот фрагмент, что позволит вам собрать файл в месте -. Если вы прервали процесс записи чанка в любой момент, вы можете перезапустить запись большого файла, рассчитав правильное смещение для любого конкретного чанка, потому что вы заранее сохранили размер каждого чанка.
19
18.03.2021, 23:03

Сksh93:

tail -n +300000001 < file 1<>; file

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

С другими оболочками вы всегда можете выполнить усечение -в -месте -вручную с помощью perl, например:

{
  tail -n +300000001 &&
    perl -e 'truncate STDOUT, tell STDOUT'
} < file 1<> file

Чтобы получить индикатор выполнения, используйтеpv:

{
  head -n 300000000 | pv -s 300000000 -lN 'Skipping 300M lines' > /dev/null &&
    cat | pv -N 'Rewriting the rest' &&
    perl -e 'truncate STDOUT, tell STDOUT'
} < file 1<> file

(с использованием head | pvи cat | pvв качестве pvотказывался бы работать, если бы его ввод и вывод указывали на один и тот же файл. pv -Sls 300000000также не будет работать, поскольку pvне оставляет указатель в файле сразу после 300000000-й строки после существования, как headделает (и требуется POSIX для файлов с возможностью поиска ). pv | catвместо cat | pvпозволил бы pvузнать, сколько ему нужно прочитать, и дать вам ETA, но в настоящее время он является подделкой, поскольку не принимает во внимание случаи, когда он не читает из . ] начало этого файла, как здесь ).

Обратите внимание, что это опасно, так как файл перезаписывается на месте.Есть вероятность, что у вас закончится место на диске, если первые 300M строк содержат дыр(не должно быть для действительного текстового файла ), а остальная часть файла занимает больше места, чем у вас есть свободное место на ФС.

8
18.03.2021, 23:03

Вы можете просто прочитать и записать файл на месте, а затем обрезать файл. Может быть даже есть способ сделать это с помощью инструментов cli, не уверен, но вот он в Java (untested ).

RandomAccessFile out = new RandomAccessFile("file.txt", "rw");
RandomAccessFile in = new RandomAccessFile("file.txt", "r");
String line = null;
long rows = 0;
while( (line=in.readLine()) != null ){
    if( rows > 300000000 ) {
        out.writeBytes(line);
        out.write('\n');
    }
    rows++;
}
in.close();
out.setLength( out.getFilePointer() );
out.close();
0
18.03.2021, 23:03

Существуют различные подходы к удалению первых строк. Я рекомендую вам разбить файл на куски, изменить их (, удалить первые строки )и снова объединить куски.

В вашем случае было бы очень опасно менять файл на месте -. Если что-то пойдет не так, у вас нет запасного варианта!

Вот мое рабочее решение(bash). Возможно, вам нужны некоторые улучшения...

function split_into_chunks {
    BIG_FILE=$1

    while [ $(stat -c %s $BIG_FILE) -gt 0 ]
    do
    CHUNK_FILE="chunk.$(ls chunk.* 2>/dev/null | wc -l)"
    tail -10 $BIG_FILE > $CHUNK_FILE
    test -s $CHUNK_FILE && truncate -s -$(stat -c %s $CHUNK_FILE) $BIG_FILE
    done
}

function concat_chunks {
    BIG_FILE=$1
    test ! -s $BIG_FILE || (echo "ERROR: target file is not empty"; return)

    for CHUNK_FILE in $(ls chunk.* | sort -t. -k2 -n -r)
    do
    cat $CHUNK_FILE >> $BIG_FILE
    rm $CHUNK_FILE
    done
}

Тест:

$ seq 1000 > big-file.txt 
$ stat -c "%s %n" chunk.* big-file.txt 2>/dev/null | tail -12
3893 big-file.txt
$ md5sum big-file.txt; wc -l big-file.txt 
53d025127ae99ab79e8502aae2d9bea6  big-file.txt
1000 big-file.txt

$ split_into_chunks big-file.txt
$ stat -c "%s %n" chunk.* big-file.txt | tail -12
40 chunk.9
31 chunk.90
30 chunk.91
30 chunk.92
30 chunk.93
30 chunk.94
30 chunk.95
30 chunk.96
30 chunk.97
30 chunk.98
21 chunk.99
0 big-file.txt

$ # here you could change the chunks
$ # the test here shows that the file will be concatenated correctly again

$ concat_chunks big-file.txt
$ stat -c "%s %n" chunk.* big-file.txt 2>/dev/null | tail -12
3893 big-file.txt
$ md5sum big-file.txt; wc -l big-file.txt 
53d025127ae99ab79e8502aae2d9bea6  big-file.txt
1000 big-file.txt

Подсказка :Вам обязательно нужно убедиться, что все ваши фрагменты не слишком малы (очень долгое время обработки )и не слишком велики (недостаточно места на диске )! В моем примере используется 10 строк на фрагмент -. Я предполагаю, что это слишком мало для вашей задачи.

2
18.03.2021, 23:03

Теги

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