Может кто-нибудь подсказать, как удалить последние n строк из файла в Perl? У меня очень большой файл размером около 400 МБ, и я хочу удалить из него около 125 000 последних строк.
Как удалить последние N строк файла?
Ответы (11)
Вы можете использовать Tie :: File для обработки файла как массива.
use Tie::File; tie (@File, 'Tie::File', $Filename); splice (@File, -125000, 125000); untie @File;
Альтернативой является использование head
и wc -l
в оболочке.
изменить: grepsedawk напоминает нам о параметре -n
для head
, wc
не требуется:
head -n -125000 FILE > NEWFILE
Поскольку люди уже предложили Tie :: Array, который хорошо выполняет свою работу, я изложу базовый алгоритм, если вы захотите сделать это вручную. Есть небрежные и медленные способы сделать это, хорошо работающие с небольшими файлами. Вот эффективный способ сделать это для больших файлов.
- Найдите позицию в файле непосредственно перед N-й строкой с конца.
- Обрежьте все, что находится после этой точки (используя
truncate()
).
1 - сложная часть. Мы не знаем, сколько строк в файле и где они находятся. Один из способов - пересчитать все строки и вернуться к N-му. Это означает, что мы должны каждый раз сканировать весь файл. Более эффективным было бы чтение в обратном направлении с конца файла. Вы можете сделать это с помощью read()
, но проще использовать File :: ReadBackwards, который можно в обратном направлении построчно (при этом все еще используются эффективные буферизованные чтения).
Это означает, что вы читаете только 125 000 строк, а не весь файл. truncate()
должен быть O (1) и атомарным и почти ничего не стоить, независимо от размера файла. Он просто сбрасывает размер файла.
#!/usr/bin/perl
use strict;
use warnings;
use File::ReadBackwards;
my $LINES = 10; # Change to 125_000 or whatever
my $File = shift; # file passed in as argument
my $rbw = File::ReadBackwards->new($File) or die $!;
# Count backwards $LINES or the beginning of the file is hit
my $line_count = 0;
until( $rbw->eof || $line_count == $LINES ) {
$rbw->readline;
$line_count++;
}
# Chop off everything from that point on.
truncate($File, $rbw->tell) or die "Could not truncate! $!";
Вы знаете, сколько в нем строк, или есть еще какие-нибудь подсказки об этом файле? Вам нужно делать это снова и снова, или это только один раз?
Если бы мне пришлось сделать это один раз, я бы загрузил файл в vim, посмотрел на последний номер строки, а затем удалил бы из последней строки, которую хочу, до конца:
:1234567,$d
Общий способ программирования состоит в том, чтобы сделать это в два прохода: один для определения количества строк, а второй - для удаления строк.
Самый простой способ - вывести нужное количество строк в новый файл. Это эффективно только с точки зрения циклов и, возможно, небольшой перегрузки диска, но у большинства людей их много. Некоторые вещи из perlfaq5 должны помочь. Вы выполняете работу и продолжаете жить.
while( ) { print $out; last if $. > $last_line_I_want; }
Если это то, что вам нужно сделать много или размер данных слишком велик для их перезаписи, вы можете создать индекс строк и байтовых смещений и truncate () файл до нужного размера. Пока вы сохраняете индекс, вам нужно только обнаруживать новые окончания строк, потому что вы уже знаете, где остановились. Некоторые модули обработки файлов могут справиться со всем этим за вас.
Я бы просто использовал сценарий оболочки для этой проблемы:
tac file | sed '1,125000d' | tac
(tac похож на cat, но печатает строки в обратном порядке. Авторы Джея Лепро и Дэвида Маккензи. Часть GNU coreutils.)
- перейти в конец файла: fseek
- посчитать в обратном порядке на столько строк
- узнать позицию файла: ftell
- обрезать файл до этой позиции как длина: ftruncate
Шверн: Нужны ли строки use Fnctl
и $rbw->get_handle
в вашем скрипте? Кроме того, я бы рекомендовал сообщать truncate
об ошибках, если он не возвращает true.
- Дуглас Хантер (который бы прокомментировал этот пост, если бы мог)
Попробуйте этот код:
my $ i = 0;
sed -i '\ $ d' filename while ($ i ++ ‹n);
обратные кавычки тоже будут там, но я не могу их распечатать :(
Мое предложение с использованием ed
:
printf '$-125000,$d\nw\nq\n' | ed -s myHugeFile
попробуй это
:|dd of=urfile seek=1 bs=$(($(stat -c%s urfile)-$(tail -1 urfile|wc -c)))
Этот пример кода сохранит индекс последних 10 строк при сканировании файла. Затем он использует самый ранний индекс в буфере, чтобы обрезать файл. Это, конечно, будет работать только в том случае, если усечение работает в вашей системе.
#! /usr/bin/env perl
use strict;
use warnings;
use autodie;
open my $file, '+<', 'test.in'; # rw
my @list;
while(<$file>){
if( @list <= 10 ){
push @list, tell $file;
}else{
(undef,@list) = (@list,tell $file);
}
}
seek $file, 0, 0;
truncate $file, $list[0] if @list;
close $file;
Это имеет дополнительное преимущество, заключающееся в том, что он использует достаточно памяти только для последних десяти индексов и текущей строки.
Самый эффективный способ - перейти к концу файла, затем постепенно прочитать сегменты, подсчитывая количество новых строк в каждом, а затем использовать усечение (см. Perldoc -f truncate), чтобы обрезать его. На CPAN также есть модуль или два для чтения файла в обратном направлении.