2016-10-26 2 views
4

私は巨大なテキストファイルと、それの最初の5行を持っているが、以下のように読み取ります

This is fist line 
This is second line 
This is third line 
This is fourth line 
This is fifth line 

を今、私は3行目のランダムな位置に何かを書きたいですその行の文字を私が書いている新しい文字列で置き換えるファイル。私は以下のコードでそれを達成することができる午前:

use strict; 
use warnings; 

my @pos = (0); 
open my $fh, "+<", "text.txt"; 

while(<$fh) { 
    push @pos, tell($fh); 
} 

seek $fh , $pos[2]+1, 0; 
print $fh "HELLO"; 

close($fh); 

しかし、私はテキストは以下の読むように、私はそのファイルから全体の三行を削除することができますどのようなアプローチの同じ種類を把握することはできませんよ:

This is fist line 
This is second line 
This is fourth line 
This is fifth line 

ファイル全体を配列に読み込む必要はなく、Tie :: Fileも使用しません。シークとティーチを使用して私の要件を達成することは可能ですか?解決策は非常に役に立つでしょう。

+0

と同じです。なぜ「Tie :: File」を使用したくないのですか?私はそれがこの目的には理想的だと思います。 – Borodin

+1

@Borodin Tie :: Fileファイルを配列に読み込んだら、メモリを消費しませんか?その場合、モジュールの-memoryオプションが役立つでしょうか? –

答えて

7

ファイルは一連のバイトです。我々はの代わりに(上書き)を置き換えることができますが、どうすればを削除しますか?ファイルが書き込まれると、そのバイトはシーケンスから「抜かれ」たり、何らかの形で「空白」になることはありません。

残りのコンテンツは「上に」移動しなければならないので、削除するテキストに続くものが上書きされます。ファイルの残りの部分を書き直さなければなりません。実際には、ファイル全体を書き換える方がずっと簡単です。非常に基本的な例として

use warnings 'all'; 
use strict; 
use File::Copy qw(move); 

my $file_in = '...'; 
my $file_out = '...'; # best use `File::Temp` 

open my $fh_in, '<', $file_in or die "Can't open $file_in: $!"; 
open my $fh_out, '>', $file_out or die "Can't open $file_out: $!"; 

# Remove a line with $pattern 
my $pattern = qr/this line goes/; 

while (<$fh_in>) 
{ 
    print $fh_out $_ unless /$pattern/; 
} 
close $fh_in; 
close $fh_out; 

# Rename the new fie into the original one, thus replacing it 
move ($file_out, $file_in) or die "Can't move $file_out to $file_in: $!"; 

線が所与のパターンと一致しない限り、これは、出力ファイルに入力ファイルのすべての行を書き込みます。その後、そのファイルの名前が変更され、元のファイル(データコピーを伴わないファイル)が置き換えられます。 this topic in perlfaq5を参照してください。

私たちは実際に一時ファイルを使用しているので、そのためにコアモジュールFile::Tempをお勧めします。


これには、ファイルの一部のみを上書きする更新'+<'モードの開口部によって、より効率的な、しかしはるかに複雑化することができます。パターンのある行まで繰り返し、その位置と行の長さを記録し(tell)、残りの行をすべてメモリにコピーします。その後seekの行の位置からその行の長さに戻って、ファイルの残りの部分をダンプし、その行とそれに続くすべてを上書きします。

残りのファイルのデータは、の2回、の2度コピーされますが、1つのコピーはメモリに保存されます。削除する行が非常に大きなファイルの下にある場合は、この問題に進むことができます。これを取り除くべき行がさらにあれば、これはより厄介になります。


新しいファイルを書き出すと、元の上にそれをコピーするには、ファイルのiノード番号を変更します。それはいくつかのツールや手順のための問題である可能性があり、それがある場合は、代わりに新しいファイルが書き出されたら、いずれかの

  • によって元の更新読み取りのためにそれを開いて、書き込み用にオリジナルを開くことができます。これは元のファイルを破壊します。その後、新しいファイルから読み込んで元のファイルに書き込んで、同じiノードにコンテンツをコピーします。完了したら、新しいファイルを削除します。

  • 読み書きモード('+<')で元のファイルを開きます。新しいファイルが書き込まれると、元の部分(または上書きする場所)の冒頭にseekが書き込まれ、新しいファイルの内容が書き込まれます。新しいファイルが短い場合は、コピーが完了した後に

    truncate $fh, tell($fh); 
    

    などのファイルの終わりを設定することを忘れないでください。これにはいくつかの注意が必要で、最初の方法はおそらく一般的に安全です。

ファイルは新しい「ファイル」は、配列や文字列として、メモリに「書かれた」ことができる巨大なかった場合。

+0

私の指摘は何もせずにその行を上書きすることはできないので、行がなくなり次の行が自動的に表示されるようにすることはできませんか? –

+2

'これは3行目です。\ nは19文字です。他の19文字で置き換えることができます。 – PerlDuck

+0

@ H.Burnsそうです、それは事です - 何もない、それはそこにあるバイトなので、いくつかの内容です。それを「削除」する唯一の方法は、残りの部分を移動することです。それぞれの中にピースが入った小さな箱の線を想像してみましょう。それぞれには何かがあります。ファイルシステムには、魔法のようにボックスを抜き出す方法はありません。私たちができることは、次のボックスの内容を "削除"したいものに移動することだけです。最後のバイトは破棄されるかもしれません。 – zdim

0

PerlでのLinuxのコマンドラインからのsedコマンドを使用: "3D" は3番目の行を削除する意味

my $return = `sed -i '3d' text.txt`; 

+0

downvoteはなぜですか? OPはperlの巨大なファイルから行を削除する方法を尋ねました。彼が望むことをします。 – papaiatis

+0

おそらくこれはまさにPerlの解決策ではなく、単にsed解決策であるためです。また、 '$ return'の内容は役に立たない。それは常に空です。 (私はdownvoterではなかった、btw。) – PerlDuck

-1

perlrunを見て、perl自身が「インプレース」ファイルをどのように変更するかを見ておくと便利です。

を考える:あなたは明らかにPerlを呼び出すために-i-pスイッチを使用することにより、同様にsedは、 'その場で修正' することができます

$ cat text.txt 
This is fist line 
This is second line 
This is third line 
This is fourth line 
This is fifth line 

を:

$ perl -i -pe 's/This is third line\s*//' text.txt 
$ cat text.txt 
This is fist line 
This is second line 
This is fourth line 
This is fifth line 

をしかし、あなたはPerlを相談している場合調理法のレシピ7.9(またはperlrunをご覧ください)、これは次のようになります:

$ perl -i -pe 's/This is third line\s*//' text.txt 

は、

while (<>) { 
    if ($ARGV ne $oldargv) {   # are we at the next file? 
     rename($ARGV, $ARGV . '.bak'); 
     open(ARGVOUT, ">$ARGV");  # plus error check 
     select(ARGVOUT); 
     $oldargv = $ARGV; 
    } 
    s/This is third line\s*//; 
} 
continue{ 
    print; 
} 
select (STDOUT);      # restore default output