Perl でファイルから最後の n 行を削除する方法のヒントを誰か教えてもらえますか? 約 400 MB の非常に大きなファイルがあり、そこから最後の 125,000 行を削除したいと考えています。
11 に答える
Tie::Fileを使用して、ファイルを配列として扱うことができます。
Tie::File を使用します。 tie (@File, 'Tie::File', $Filename); スプライス (@ファイル、-125000、125000); @File を解きます。
別の方法は、シェルでhead
andを使用することです。wc -l
編集: grepsedawk は、不要な-n
オプションを思い出させます:head
wc
head -n -125000 ファイル > NEWFILE
人々がすでにTie::Arrayを提案しているので、これはうまく機能します。手動で実行したい場合は、基本的なアルゴリズムをレイアウトします。小さなファイルにうまく機能する、ずさんな、遅い方法があります。大きなファイルに対してこれを行う効率的な方法は次のとおりです。
- ファイル内の最後からN行目の直前の位置を見つけます。
- それ以降はすべて切り捨てます(を使用
truncate()
)。
1はトリッキーな部分です。ファイルに何行あるのか、どこにあるのかわかりません。1つの方法は、すべての行を数えてからN番目に戻ることです。これは、毎回ファイル全体をスキャンする必要があることを意味します。より効率的なのは、ファイルの終わりから逆方向に読み取ることです。これはで行うことができますが、File :: ReadBackwardsread()
を使用する方が簡単です。これにより、行ごとに逆方向に移動できます(ただし、効率的なバッファー読み取りを使用します)。
これは、ファイル全体ではなく、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
一般的なプログラミング方法は、2 つのパスで行うことです。1 つは行数を決定するためのもので、もう 1 つは行を削除するためのものです。
簡単な方法は、正しい行数を新しいファイルに出力することです。サイクルとディスクのスラッシングに関してのみ効率的ですが、ほとんどの人はそれらをたくさん持っています。perlfaq5の一部が役立つはずです。あなたは仕事をやり遂げ、人生を歩み始めます。
その間( ) { $out; を印刷します。 $の場合は最後。> $last_line_I_want; }
これが頻繁に行う必要がある場合、またはデータ サイズが大きすぎて書き直すことができない場合は、行とバイト オフセットのインデックスを作成し、ファイルを適切なサイズにtruncate()することができます。インデックスを保持しているため、中断した場所が既にわかっているため、新しい行末を見つけるだけで済みます。一部のファイル処理モジュールは、そのすべてを処理できます。
この問題にはシェルスクリプトを使用します。
tac file | sed '1,125000d' | tac
(tac は cat に似ていますが、行を逆順に出力します。Jay Lepreau と David MacKenzie による。GNU coreutils の一部です。)
- ファイルの最後に移動: fseek
- その行数を逆算する
- ファイルの位置を調べる: ftell
- 長さとしてその位置までファイルを切り捨てます: ftruncate
これを試して
:|dd of=urfile seek=1 bs=$(($(stat -c%s urfile)-$(tail -1 urfile|wc -c)))
このコードを試してください:
私の$i= 0;
sed -i'\ $ d'ファイル名while($ i ++ <n);
バッククォートもありますが、印刷することはできません:(
を使用した私の提案ed
:
printf '$-125000,$d\nw\nq\n' | ed -s myHugeFile
このコード例は、ファイルをスキャンするときに、最後の 10 行のインデックスを保持します。次に、バッファ内の最も古いインデックスを使用して、ファイルを切り捨てます。もちろん、これはシステムで truncate が機能する場合にのみ機能します。
#! /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;
これには、最後の 10 個のインデックスと現在の行に十分なメモリしか使用しないという追加の利点があります。
Schwern: スクリプトの行use Fnctl
と$rbw->get_handle
行は必要ですか? truncate
また、 true が返されない場合はエラーを報告することをお勧めします。
-- Douglas Hunter (できればその投稿にコメントしただろう)
最も効率的な方法は、ファイルの最後までシークし、各セグメントの改行数をカウントしながらセグメントを段階的に読み取り、切り捨て(perldoc -f truncateを参照)を使用して切り詰めることです。CPANには、ファイルを逆方向に読み取るためのモジュールも1つまたは2つあります。