-i
オプションを使うには?tail -f
をするには?-i
clobberはファイルをプロテクトするの? これは Perl のバグじゃないの?perlfaq5 - ファイルとフォーマット
このセクションでは、入出力と“f”に関する事柄: ファイルハンドル (filehandle)、フラッシング(flushing)、フォーマット(format)、 フッター(footer)を扱います。
(brian d foy によって寄贈されました)
http://perl.plover.com/FAQs/Buffering.html にある Mark Jason Dominus の "Suffering From Buffering" を読みたいかもしれません。
通常 Perl は出力をバッファリングするので、ちょっと出力する毎に システムコールを呼び出したりはしません。 出力回数を節約することで、負荷の高いシステムコールの回数を減らします。 例えば、このコード片では、プログラムが1行の処理する毎に、プログラムの 実行状況を示すためにドットを表示します。 Perl は 行毎にドットを表示せず、出力をバッファリングするので、 長い間待った後に 50 文字のドットが一気に表示されます:
# long wait, then row of dots all at once
while( <> ) {
print ".";
print "\n" unless ++$count % 50;
#... expensive line processing operations
}
これを回避するには、出力ファイルハンドル (この場合は STDOUT
) の バッファリングを留める必要があります。 特殊変数 $|
を真の値にセットします (記憶法: パイプをホットな状態にしておくために使う。):
$|++;
# dot shown immediately
while( <> ) {
print ".";
print "\n" unless ++$count % 50;
#... expensive line processing operations
}
$|
はファイルハンドル単位の特殊変数の一つで、ファイルハンドル毎に この値のコピーを保持しています。 例えば、標準出力と標準エラーをまとめたいなら、それぞれのバッファリングを 解除する必要があります(但し、STDERR はデフォルトでは バッファリングしていないはずです):
{
my $previous_default = select(STDOUT); # save previous default
$|++; # autoflush STDOUT
select(STDERR);
$|++; # autoflush STDERR, to be sure
select($previous_default); # restore previous default
}
# now should alternate . and +
while( 1 )
{
sleep 1;
print STDOUT ".";
print STDERR "+";
print STDOUT "\n" unless ++$count % 25;
}
$|
特殊変数に加えて、binmode
を使ってファイルハンドルに :unix
層をつけることでバッファリングしないようにできます:
binmode( STDOUT, ":unix" );
while( 1 ) {
sleep 1;
print ".";
print "\n" unless ++$count % 50;
}
出力層に関するさらなる情報については、perlfunc の binmode
および open
のエントリと、PerlIO
モジュールの文書を参照してください。
IO::Handle
かその派生クラスを使っているなら、ファイルハンドルの設定を 変更するために autoflush
メソッドを呼び出せます:
use IO::Handle;
open my( $io_fh ), ">", "output.txt";
$io_fh->autoflush(1);
IO::Handle
オブジェクトには flush
メソッドもあります。 いつでも自動バッファリングなしにバッファをフラッシュできます
$io_fh->flush;
(brian d foy によって寄贈されました)
テキストファイルの行を挿入、変更、削除するための基本的な考え方は、 ファイルを変更したい地点まで読み込んで表示し、変更を行い、ファイルの 残りを読み込んで表示するというものです。 (特にレコード入力セパレータ $/
は変更可能なので) Perl は行に対する ランダムアクセスは提供していませんが、Tie::File
はそれを見せかけます。
この作業をする Perl プログラムは、ファイルを開いて、一行毎に表示し、 ファイルを閉じるという基本的な形をとります:
open my $in, '<', $file or die "Can't read old file: $!";
open my $out, '>', "$file.new" or die "Can't write new file: $!";
while( <$in> )
{
print $out $_;
}
close $out;
基本形の中に、行の追加、修正、削除という必要な作業を追加します。
先頭に追加するには、既存の行を表示するループに入る前に追加行を表示します。
open my $in, '<', $file or die "Can't read old file: $!";
open my $out, '>', "$file.new" or die "Can't write new file: $!";
print $out "# Add this line to the top\n"; # <--- HERE'S THE MAGIC
while( <$in> )
{
print $out $_;
}
close $out;
既にある行を変更するには、while
の中に行を変更するコードを追加します。 今回の場合、コードは小文字の "perl" を探してそれを大文字にします。 これは全ての行に対して起こるので、全ての行に対してこれを行いたいということを 確認してください!
open my $in, '<', $file or die "Can't read old file: $!";
open my $out, '>', "$file.new" or die "Can't write new file: $!";
print $out "# Add this line to the top\n";
while( <$in> )
{
s/\b(perl)\b/Perl/g;
print $out $_;
}
close $out;
特定の行だけ変更するには、入力行番号 $.
が便利です。 まず、変更したい行まで読み込んで表示します。 次に、変更したい行を 1 行読んで、変更し、表示します。 その後、残りの行を読み込んで表示します:
while( <$in> ) # print the lines before the change
{
print $out $_;
last if $. == 4; # line number before change
}
my $line = <$in>;
$line =~ s/\b(perl)\b/Perl/g;
print $out $line;
while( <$in> ) # print the rest of the lines
{
print $out $_;
}
行を読み飛ばすには、ループ制御を使います。 この例の next
はコメント行を読み飛ばし、last
は、__END__
か __DATA__
のどちらかに出会った後は全ての処理を停止します。
while( <$in> )
{
next if /^\s+#/; # skip comment lines
last if /^__(END|DATA)__$/; # stop at end of code marker
print $out $_;
}
同じようなことは、next
を使って表示したくない行を飛ばすことで行えます。
while( <$in> )
{
next unless $. % 5;
print $out $_;
}
もし、なんらかの変わった理由により、一行ずつ処理するのではなく、本当に ファイル全体を一度に読み込みたい場合、(メモリがあれば!)以下のようにして できます:
open my $in, '<', $file or die "Can't read old file: $!"
open my $out, '>', "$file.new" or die "Can't write new file: $!";
my @lines = do { local $/; <$in> }; # slurp!
# do your magic here
print $out @lines;
File::Slurp
や Tie::File
のようなモジュールもこの助けになります。 しかし、もし可能なら、ファイル全体を一度に読み込むのは避けてください。 Perl は OS から確保したメモリを、プロセスが終わるまで返しません。
ファイルをその場で変更するために、Perl の一行野郎も使えます。 以下の例では、inFile.txt にある全ての 'Fred' を 'Barney' に変更し、 ファイルを新しい内容で上書きします。 -p
スイッチ付きの場合、-e
で指定したコードを while
ループで包み、 -i
はその場編集を有効にします。 現在行は $_
に入っています。 -p
があると、Perl はループの最後に $_
の値を自動的に表示します。 さらなる詳細については perlrun を参照してください。
perl -pi -e 's/Fred/Barney/' inFile.txt
inFile.txt
をバックアップするには、-i
で追加する拡張子を指定します:
perl -pi.bak -e 's/Fred/Barney/' inFile.txt
5 行目だけを変更するには、入力行番号 $.
をチェックするテストを追加し、 テストに通過した場合にのみ操作を行います:
perl -pi -e 's/Fred/Barney/ if $. == 5' inFile.txt
特定の行の前に追加するには、Perl が $_
を表示する前に行を追加します:
perl -pi -e 'print "Put before third line\n" if $. == 3' inFile.txt
現在行はループの終わりに表示されるので、ファイルの先頭に行を 追加することもできます:
perl -pi -e 'print "Put before first line\n" if $. == 1' inFile.txt
既にファイルにある行の後に行を追加するには、-n
スイッチを使います。 これは -p
と同様ですが、ループの終わりに $_
を表示しないので、 自分自身で表示する必要があります。 この場合、まず $_
を表示し、それから追加したい行を表示します。
perl -ni -e 'print; print "Put after fifth line\n" if $. == 5' inFile.txt
行を削除するには、必要なものだけを表示します:
perl -ni -e 'print unless /d/' inFile.txt
... or ...
perl -pi -e 'next unless /d/' inFile.txt
(brian d foy によって寄贈されました)
概念的には、ファイルの行数を数える一番易しい方法は単にそれを読み込んで それを数えます:
my $count = 0;
while( <$fh> ) { $count++; }
しかし、Perl には最後に読み込んだファイルハンドルの現在の行番号である $.
変数が既にあるので、自分自身で数える必要はありません:
1 while( <$fh> );
my $count = $.;
$.
を使いたいなら、以下のように単純な一行野郎にまで短くできます:
% perl -lne '} print $.; {' file
% perl -lne 'END { print $. }' file
しかしこれらはやや非効率です。 これがあなたにとって十分に速くないなら、単にデータの塊を読み込んで改行の 数を数えるかもしれません:
my $lines = 0;
open my($fh), '<:raw', $filename or die "Can't open $filename: $!";
while( sysread $fh, $buffer, 4096 ) {
$lines += ( $buffer =~ tr/\n// );
}
close FILE;
しかし、これは行の末尾が改行でない場合は動作しません。 tr///
を s///
に変えて、入力レコードセパレータ $/
が 現れた数を数えるかもしれません:
my $lines = 0;
open my($fh), '<:raw', $filename or die "Can't open $filename: $!";
while( sysread $fh, $buffer, 4096 ) {
$lines += ( $buffer =~ s|$/||g; );
}
close FILE;
シェルに出るのを気にしないなら、wc
コマンドが、プロセス間の オーバーヘッドがあっても普通は最速です。 しかし、汚染されていないファイル名を使うように気をつけてください:
#!perl -T
$ENV{PATH} = undef;
my $lines;
if( $filename =~ /^([0-9a-z_.]+)\z/ ) {
$lines = `/usr/bin/wc -l $1`
chomp $lines;
}
(brian d foy によって寄贈されました)
概念的に最も簡単な解決法は、ファイルの行数を数えて、最初から行数分 (マイナス最後の N) だけ新しいファイルに出力します。
ほとんどの場合、実際の質問は、ファイルに対して 1 パスで最後の N 行を 削除する方法、または多くのコピーなしに行う方法です。 簡単な概念は、ファイルに何百万行もある場合は現実性がありません。
一つの裏技は File::ReadBackwards
を使って、ファイルの最後から 始めることです。 このモジュールは、ファイルを動き回りやすくするために実際の ファイルハンドルをラップしたオブジェクトを提供します。 必要な場所が分かれば、実際のファイルハンドルを取得して、いつも通りに 処理します。 この場合、保持したい最後の行の最後のファイル位置を取得して、 その位置でファイルを切り詰めます:
use File::ReadBackwards;
my $filename = 'test.txt';
my $Lines_to_truncate = 2;
my $bw = File::ReadBackwards->new( $filename )
or die "Could not read backwards in [$filename]: $!";
my $lines_from_end = 0;
until( $bw->eof or $lines_from_end == $Lines_to_truncate )
{
print "Got: ", $bw->readline;
$lines_from_end++;
}
truncate( $filename, $bw->tell );
File::ReadBackwards
モジュールは、入力レコードセパレータを 正規表現で設定できるという利点もあります。
行に対して tie された配列を使ってアクセスできる Tie::File
モジュールも 使えます。 最後の添え字を設定して splice
を使うなど、、ファイルを修正するために 通常の配列操作が使えます。
-i
オプションを使うには?-i
は Perl の $^I
変数の値をセットし、これにより <>
の 振る舞いに影響を与えます; 更なる詳細については perlrun を 参照してください。 適切な変数を直接修正することによって、より大きなプログラムの中で同じ効果が 得られます。 例えば:
# ...
{
local($^I, @ARGV) = ('.orig', glob("*.c"));
while (<>) {
if ($. == 1) {
print "This line should appear at the top of each file\n";
}
s/\b(p)earl\b/${1}erl/i; # Correct typos, preserving case
print;
close ARGV if eof; # Reset $.
}
}
# $^I and @ARGV return to their old values here
このブロックはカレントディレクトリの全ての the .c
ファイルを修正し、 各ファイルの元データのバックアップを新しい .c.orig
ファイルに残します。
(brian d foy によって寄贈されました)
File::Copy
モジュールをつかいましょう。 これは Perl に同梱されていて、ファイルシステム間で真のコピーが行われ、 移植性の面での細工がされています。
use File::Copy;
copy( $original, $new_copy ) or die "Copy failed: $!";
File::Copy
が使えない場合、作業を自分自身でする必要があります: 元ファイルを開き、宛て先ファイルを開き、元ファイルから読んだものを宛て先 ファイルに書き込みます。 権限、所有者、グループも新しいファイルにコピーすることを忘れないように しなければなりません。
もしファイル名を知る必要がないなら、open()
でファイル名の部分に undef
を指定します。 Perl 5.8 以降では、open()
関数は無名一時ファイルを作成します。
open my $tmp, '+>', undef or die $!;
さもなければ、File::Temp モジュールが使えます。
use File::Temp qw/ tempfile tempdir /;
my $dir = tempdir( CLEANUP => 1 );
($fh, $filename) = tempfile( DIR => $dir );
# or if you don't need to know the filename
my $fh = tempfile( DIR => $dir );
File::Temp は Perl 5.6.1 以降標準モジュールになっています。 十分にモダンな Perl がインストールされていないなら、 IO::File モジュールにあるクラスメソッド new_tmpfile
を使って 読み書きのためにオープンされたファイルハンドルを取得します。 ファイルがどんな名前なのかを知る必要がない場合はこれを使ってください。
use IO::File;
my $fh = IO::File->new_tmpfile()
or die "Unable to make new temporary file: $!";
一時ファイルの作成を手作業で行いたいのなら、 プロセスIDやその時点での時刻(あるいはこれら両方)を使用してください。 一つのプロセスで複数の一時ファイルを使用するのであれば、 カウンターを使用しましょう:
BEGIN {
use Fcntl;
my $temp_dir = -d '/tmp' ? '/tmp' : $ENV{TMPDIR} || $ENV{TEMP};
my $base_name = sprintf "%s/%d-%d-0000", $temp_dir, $$, time;
sub temp_file {
my $fh;
my $count = 0;
until( defined(fileno($fh)) || $count++ > 100 ) {
$base_name =~ s/-(\d+)$/"-" . (1 + $1)/e;
# O_EXCL is required for security reasons.
sysopen $fh, $base_name, O_WRONLY|O_EXCL|O_CREAT;
}
if( defined fileno($fh) ) {
return ($fh, $base_name);
}
else {
return ();
}
}
}
最も効率的なやり方はpack() と unpack() を使うものです。 これは文字列が大量にあるときにはsubstr() を 使うよりも高速です。 速度低下もほとんどありません。
以下に挙げたのは幾つかの固定フォーマットをした入力行に対して 分解を行ったり書き戻しをするコード片の例で、出力は Berkeley形式のpsに準じています。
# sample input line:
# 15158 p5 T 0:00 perl /home/tchrist/scripts/now-what
my $PS_T = 'A6 A4 A7 A5 A*';
open my $ps, '-|', 'ps';
print scalar <$ps>;
my @fields = qw( pid tt stat time command );
while (<$ps>) {
my %process;
@process{@fields} = unpack($PS_T, $_);
for my $field ( @fields ) {
print "$field: <$process{$field}>\n";
}
print 'line=', pack($PS_T, @process{@fields} ), "\n";
}
それぞれの行のフィールドを簡単に扱うためにハッシュスライスを使いました。 キーを配列に保管するということで、これらをグループや for
によるループで 簡単に扱えます。 これはまた、グローバル変数とシンボリックリファレンスの使用でプログラムが 汚染されることを防ぎます。
perl5.6 から、open() にファイルハンドルやディレクトリハンドルとして未定義の スカラ変数を渡すと、これをリファレンスとして自動有効化します。 その後これらのリファレンスはその他のスカラと同じように扱え、名前付き ハンドルを指定する場所で使えます。
open my $fh, $file_name;
open local $fh, $file_name;
print $fh "Hello World!\n";
process_file( $fh );
お好みなら、これらのファイルハンドルを配列やハッシュに保管することもできます。 これらを直接アクセスすると、これらは単なるスカラではないので、 ファイルハンドルを中かっこで囲むことで print
に少し助けを与える必要が あります。 Perl はファイルハンドルリファレンスが単純なスカラの場合にのみファイル ハンドルリファレンスと認識します。
my @fhs = ( $fh1, $fh2, $fh3 );
for( $i = 0; $i <= $#fhs; $i++ ) {
print {$fhs[$i]} "just another Perl answer, \n";
}
perl5.6 より前では、古いコードにあるような、様々な型グロブの慣用法を 扱わなければなりません。
open FILE, "> $filename";
process_typeglob( *FILE );
process_reference( \*FILE );
sub process_typeglob { local *FH = shift; print FH "Typeglob!" }
sub process_reference { local $fh = shift; print $fh "Reference!" }
大量の無名ハンドルを作りたいのであれば、 Symbol, FileHandle, IO::Handle といったモジュールを参照してください。
間接ファイルハンドルは、あるファイルハンドルを期待している場所に置かれた シンボル以外のなにかを使っています。 以下に間接ファイルハンドルの例を挙げます:
$fh = SOME_FH; # bareword is strict-subs hostile
$fh = "SOME_FH"; # strict-refs hostile; same package only
$fh = *SOME_FH; # typeglob
$fh = \*SOME_FH; # ref to typeglob (bless-able)
$fh = *SOME_FH{IO}; # blessed IO::Handle from *SOME_FH typeglob
あるいは、FileHandleモジュールやIOモジュールのnew
メソッドを使い、 それをスカラー変数に格納します。 同じように扱います。
use IO::Handle; # 5.004 or higher
my $fh = IO::Handle->new();
上記のように作成して、後は普通のファイルハンドルと同じように使います。 Perlがファイルハンドルを期待しているところではどこでも 間接ファイルハンドルを使うことができるでしょう。 間接ファイルハンドルは、ファイルハンドルを保持しているスカラー変数に すぎません。print
, open
, seek
のような関数や、 ダイヤモンド演算子 <FH>
はファイルハンドルもファイルハンドルを 保持しているスカラー変数の両方とも受け付けます。
($ifh, $ofh, $efh) = (*STDIN, *STDOUT, *STDERR);
print $ofh "Type it: ";
my $got = <$ifh>
print $efh "What was that: $got";
ファイルハンドルを関数に渡した場合、渡される関数は二種類の書き方ができます:
sub accept_fh {
my $fh = shift;
print $fh "Sending to indirect filehandle\n";
}
型グロブを局所化して、ファイルハンドルを直接使うことも可能です:
sub accept_fh {
local *FH = shift;
print FH "Sending to localized filehandle\n";
}
両方の形式とも、オブジェクトでも実際のファイルハンドルの型グロブでも動作します (ある場合においては文字列でも可能ですが、これはちょっとリスクがあります)。
accept_fh(*STDOUT);
accept_fh($handle);
上の例では、ファイルハンドルを使う前にそれをスカラー変数に代入していました。 これは式でもなく、ハッシュや配列の添え字でもなく単純スカラ変数だけが print
やprintf
、ダイヤモンド演算子と一緒に使えるからです。 単純スカラ変数以外のものをファイルハンドルとして使うのは不正であり、 コンパイル時にエラーとなります:
my @fd = (*STDIN, *STDOUT, *STDERR);
print $fd[1] "Type it: "; # WRONG
my $got = <$fd[0]> # WRONG
print $fd[2] "What was that: $got"; # WRONG
print
やprintf
の場合には、ブロックを使ってその中にファイルハンドルを 含む式を置くことによって対処することができます:
print { $fd[1] } "funny stuff\n";
printf { $fd[1] } "Pity the poor %x.\n", 3_735_928_559;
# Pity the poor deadbeef.
これらのブロックは妥当なものですから、より複雑なコードをその中に 入れこむことができます。 以下の例はメッセージを二ヶ所のどちらかひとつに送り出します:
my $ok = -x "/bin/cat";
print { $ok ? $fd[1] : $fd[2] } "cat stat $ok\n";
print { $fd[ 1+ ($ok || 0) ] } "cat stat $ok\n";
このアプローチはprint
やprintf
をオブジェクトメソッドの呼び出しのように 扱うものですが、これはダイヤモンド演算子には使えません。 なぜなら、ダイヤモンド演算子はカンマなしの引数を取る関数ではなくて本当の 演算子だからです。 さて、上記の例のように型グロブをあなたの作った構造に格納したとしましょう。 readline
という名前の組み込み関数を使って<>
が行うように レコードを読み込むことができます。 @fdを例にあったように初期化してやることでうまく動作します。 しかし、readline()は型グロブを要求するからです。 これはオブジェクトや文字列では動作しません。 このことはバグとも言えるもので現時点では修正されていません。
$got = readline($fd[0]);
間接ファイルハンドルの妙な点はそれが文字列であるか、型グロブであるか、 オブジェクトであるか、はたまた別の何者であるかには関係しないということに 注意してください。 これは基本的な演算子の構文なのです。 オブジェクトをいじくりまわして遊ぶことはここでは何の助けにもなりません。
これを行うための組み込みの方法はありません。 しかしperlformにはこれを可能にするための、大胆不敵なハッカー向けの いくつかのテクニックがあります。
(brian d foy によって寄贈されました)
文字列に write
したいなら、単に文字列へのファイルハンドルを open
する必要があります; これは Perl 5.6 から可能です:
open FH, '>', \my $string;
write( FH );
良いプログラマになりたいために、デフォルトフォーマット名がファイルハンドル名を とるためにフォーマットは裸の単語のファイルハンドルで動作するように 設計されていても、おそらくレキシカルファイルハンドルを使いたいでしょう。 しかし、これはいくつかのファイルハンドル単位 Perl 特殊変数で 制御できます; $^
は先頭ページのフォーマットの名前で、 $~
は行フォーマットです。 これらの変数を設定するにはデフォルトファイルハンドルを変更する必要があります:
open my($fh), '>', \my $string;
{ # set per-filehandle variables
my $old_fh = select( $fh );
$~ = 'ANIMAL';
$^ = 'ANIMAL_TOP';
select( $old_fh );
}
format ANIMAL_TOP =
ID Type Name
.
format ANIMAL =
@## @<<< @<<<<<<<<<<<<<<
$id, $type, $name
.
write はレキシカル変数やパッケージ変数でも動作しますが、使う変数は フォーマットのスコープでなければなりません。 これはおそらくいくつかのパッケージ変数を ローカル化したいだろうということです:
{
local( $id, $type, $name ) = qw( 12 cat Buster );
write( $fh );
}
print $string;
formline
および加算器変数 $^A
に関するいくつかのトリックもありますが、 formline
はページングなどが扱えないので、フォーマットの多くの値を 失います。 これらを使うと、最終的にフォーマットを再実装することに鳴ります。
(Peter J. Holzer, hjp-usenet2@hjp.at によって寄贈されました)
Perl 5.8.0 から、ファイル名の代わりに文字列へのリファレンスで open を呼び出すことによって、文字列を参照するファイルハンドルを作れます。 このファイルハンドルは文字列を読み書きするために使えます:
open(my $fh, '>', \$string) or die "Could not open string for writing";
print $fh "foo\n";
print $fh "bar\n"; # $string now contains "foo\nbar\n"
open(my $fh, '<', \$string) or die "Could not open string for reading";
my $x = <$fh>; # $x now contains "foo\n"
Perl のより古いバージョンでは、IO::String
モジュールが似たような機能を 提供します。
(brian d foy と Benjamin Goldberg によって寄贈されました)
数値を区切るために Number::Format が使えます。 これは代わりにピリオド(または実際のところは使いたいもの何でも)を 挿入したい人のために、ロケール情報を扱います。
このサブルーチンは数値にカンマを追加します:
sub commify {
local $_ = shift;
1 while s/^([-+]?\d+)(\d{3})/$1,$2/;
return $_;
}
Benjamin Goldberg による正規表現は数値にカンマを追加します:
s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g;
コメント付きで見ればより簡単になります:
s/(
^[-+]? # beginning of number.
\d+? # first digits before first comma
(?= # followed by, (but not included in the match) :
(?>(?:\d{3})+) # some positive multiple of three digits.
(?!\d) # an *exact* multiple, not x * 3 + 1 or whatever.
)
| # or:
\G\d{3} # after the last group, get three digits
(?=\d) # but they have to have more digits after them.
)/$1,/xg;
perlfuncで説明されている <> (glob()
) を使います。 5.6 より古いバージョンの Perl では、チルダを展開するシェルが既に インストールされていることを要求します。 最近のバージョンの Perl は内部にこの機能を持っています。 Glob::KGlob
モジュール(CPANで入手可能)はより移植性のある glob 機能を提供します。
Perl を使えば、これを以下のように直接的に行えます。
$filename =~ s{
^ ~ # find a leading tilde
( # save this in $1
[^/] # a non-slash character
* # repeated 0 or more times (0 means me)
)
}{
$1
? (getpwnam($1))[7]
: ( $ENV{HOME} || $ENV{LOGDIR} )
}ex;
ファイルを切り詰めて、その後で読み書きアクセスを提供するようなものを 使おうとしたからです。
open my $fh, '+>', '/path/name'; # WRONG (almost always)
おっと。 ファイルがなかったときに失敗するような以下のやり方を使うべきでしょう:
open my $fh, '+<', '/path/name'; # open for update
">"を使うと常に切り詰めか作成が行われます。 "<"を使った場合にはどちらも行いません。 "+"はこれらを変更することはありません。
ファイルをオープンする多くのやり方の例を以下に挙げます。 sysopen
を使っているものはすべて Fcntl
から定数を引いているものと 仮定します:
use Fcntl;
ファイルを読み込みのためにオープンするには:
open my $fh, '<', $path or die $!;
sysopen my $fh, $path, O_RDONLY or die $!;
ファイルを書き出しのためにオープンし、ファイルがなければ新しく作り あれば古いファイルを切り詰めるには:
open my $fh, '>', $path or die $!;
sysopen my $fh, $path, O_WRONLY|O_TRUNC|O_CREAT or die $!;
sysopen my $fh, $path, O_WRONLY|O_TRUNC|O_CREAT, 0666 or die $!;
ファイルを書き出しのためにオープンし、ファイルを新たに作成するが 慈善に存在していてはいけない場合:
sysopen my $fh, $path, O_WRONLY|O_EXCL|O_CREAT or die $!;
sysopen my $fh, $path, O_WRONLY|O_EXCL|O_CREAT, 0666 or die $!;
ファイルを追加のためにオープンし、必要があればファイルを作成するには:
open my $fh, '>>' $path or die $!;
sysopen my $fh, $path, O_WRONLY|O_APPEND|O_CREAT or die $!;
sysopen my $fh, $path, O_WRONLY|O_APPEND|O_CREAT, 0666 or die $!;
ファイルを追加のためにオープンするが、ファイルが事前になければいけない場合:
sysopen my $fh, $path, O_WRONLY|O_APPEND or die $!;
ファイルを更新のためにオープンするが、ファイルが事前になければいけない場合:
open my $fh, '+<', $path or die $!;
sysopen my $fh, $path, O_RDWR or die $!;
ファイルを更新のためにオープンし、必要があればファイルを作成する場合:
sysopen my $fh, $path, O_RDWR|O_CREAT or die $!;
sysopen my $fh, $path, O_RDWR|O_CREAT, 0666 or die $!;
ファイルを更新のためにオープンするが、ファイルが事前に存在してはいけない場合:
sysopen my $fh, $path, O_RDWR|O_EXCL|O_CREAT or die $!;
sysopen my $fh, $path, O_RDWR|O_EXCL|O_CREAT, 0666 or die $!;
ブロッキングなしでフィルをオープンし、必要があれば作成するには:
sysopen my $fh, '/foo/somefile', O_WRONLY|O_NDELAY|O_CREAT
or die "can't open /foo/somefile: $!":
ファイルの作成や削除はNFS越しの場合にはアトミックな操作ではないことに 注意してください。 つまり、二つのプロセスが同じファイルの作成や削除に成功するかもしれないのです! したがってO_EXCLはあなたが期待しているようには排他的ではないのです。
perlopentut も参照して下さい。
<*> 演算子はグロブ操作(globbing operation)を行います。 v5.6.0 以前の Perl では、glob() はcsh(1)を起動して、 実際のグロブを行います。 しかし、cshは127を越えるアイテムを扱うことができないので、 そういったエラーメッセージ Argument list too long
となるのです。 cshをcshとしてインストールしている人はこの問題に直面することはないでしょうが、 ユーザーがびっくりすることがあるかもしれません。
これに対処するには、Perl v5.6.0 以降にアップグレードするか グロブをreaddir()とパターンを使って 自分自身で実行するか、あるいはGlob::KGlobのようなモジュールを使って、 シェルによるグロブを行わないようにします。
(brian d foy によって寄贈されました)
Perl 5.6.0 から、glob
外部リソースに頼ることなく内部で実装されています。 当然として、最近の perl では glob
ではメモリの問題はありません。
(Brian McCauley によって寄贈されました)
Perl の open() 関数の特殊な 2 引数形式はファイル名の末尾の空白を無視し、 先頭の文字 (あるいは末尾の "|") からモードを推論します。 古いバージョンの Perl ではこれが open() の唯一の形式なので、古いコードや 本には普及しています。
2 引数形式を使う特有の理由がない限り、ファイル名のどの文字も特別なものとして 扱わない 3 引数形式の open() を使うべきです。
open my $fh, "<", " file "; # filename is " file "
open my $fh, ">", ">file"; # filename is ">file"
あなたの使っているオペレーティングシステムが、適切な mv(1) あるいは それと機能的に等価なプログラムをサポートしているのなら以下のような やり方が使えるでしょう:
rename($old, $new) or system("mv", $old, $new);
File::Copy モジュールを使うのが、より移植性があるかもしれません。 新しい名前で新しいファイルにコピーして(ここで戻り値をチェック)、 古いものを削除するだけです。 ただし、これはパーミッション、タイムスタンプ、inode情報といった メタ情報が保存されないので、rename() と同じにはなりません。
Perlに組み込みの flock関数(詳しくはperlfuncを参照)は、flock(2)が あればそれを、なければfcntl(2)を呼び出します(5.004以降の場合)。 そして、これら二つのシステムコールのいずれもない場合にはlockf(3)を 呼び出します。 一部のシステムでは、ネイティブなロッキングとは異なった使い方を するかもしれません。 以下に、Perlのflock()に関する罠(gotchas)を挙げておきます:
三つのシステムコールがどれもなければ(もしくは等価なものがなければ)、 致命的エラーを生成します。
lockf(3)は共有ロックをサポートしません。そして、書き込み用に(もしくは 追加モードか読み書きモード)オープンされているファイルハンドルを要求します。
flock()の一部のバージョンはネットワーク越し(NFSファイルシステムなど)に ファイルをロックすることはできません。 このため、Perlをビルドするときにfcntl(2)を使うように強制する必要が あるかもしれません。しかしこれでも最善かどうかは疑わしいです。 perlfuncのflock()のエントリと、そのためのPerlをビルドする 情報のある、ソース配布中にあるINSTALLというファイルを参照してください。
二つの潜在的に明らかでないけれども、伝統的な flock の手法があります。 一つはロックが与えられるまで無限に待ち、そしてそのロックは めったに忠告されません。このような自由度の高いロックはより柔軟ですが、 得るものはより少ないです。 つまり、 flock() でロックされたファイルは flock() を使っていない プログラムによって修正される可能性があるからです。 赤信号で止まる車同士ならうまくいきますが、片方が信号を守らないなら うまくいかないということです。 詳細については perlport man ページ、使っているバージョン独自のドキュメント、 システム独自のローカルな man ページを参照してください。 移植性のあるプログラムのためには、伝統的な振る舞いを仮定するのが最善です。 (そうしないなら、あなたは自分のシステムの癖(「仕様」とも呼ばれます)に 合わせて書くことをいつでも気にしないようにするべきです。 あなたの仕事をやり遂げる時に、盲目的に移植性を考慮するべきではありません。)
ファイルのロッキングに関する詳細は<perlopentut/"FileLocking">を 参照してください(5.6 対応)。
以下のようなことを してはいけません:
sleep(3) while -e 'file.lock'; # PLEASE DO NOT USE
open my $lock, '>', 'file.lock'; # THIS BROKEN CODE
これは古典的な競合状態です: あなたはここで、 一つのステップでやらなければならないことを二つのステップでやっています。 つまりこれが、コンピューターのハードウェアがアトミックな test-and-set の命令を備えている理由です。 理論的には、これを動作するにさせるには:
sysopen my $fh, "file.lock", O_WRONLY|O_EXCL|O_CREAT
or die "can't open file.lock: $!";
残念なことに、NFSを通じた場合にはファイルの作成(と削除)は アトミックではありません。 このため、この例はネットワークを通した場合にはうまく動きません (少なくとも失敗する可能性があります)。 link()を含め、様々なやり方が既に提案されていますが、これらは busy-waitを 伴うものでありあまり望ましいものではありません。
これまでただの一人もあなたにwebページのヒットカウンターは役たたずなんだと いうことを言わなかったんですか? ヒットカウンターはヒットした数は数えず、時間を浪費し、さらに言えば 作者のうぬぼれを叩くのに役立つだけです。 乱数を取り出したほうがよっぽどましです。
まあいずれにしろ、以下のようにしてやります:
use Fcntl qw(:DEFAULT :flock);
sysopen my $fh, "numfile", O_RDWR|O_CREAT or die "can't open numfile: $!";
flock $fh, LOCK_EX or die "can't flock numfile: $!";
my $num = <$fh> || 0;
seek $fh, 0, 0 or die "can't rewind numfile: $!";
truncate $fh, 0 or die "can't truncate numfile: $!";
(print $fh $num+1, "\n") or die "can't write numfile: $!";
close $fh or die "can't close numfile: $!";
以下の例はもっと良い web ページヒットカウンターです:
$hits = int( (time() - 850_000_000) / rand(1_000) );
カウントがあなたの友達を感心させないのなら、このコードもそうでしょうね :-)
もし flock()
が正しく実装されているシステムを使っていて、 "perldoc -f flock" にある追加コードの例を使っているなら、たとえ使っている OS が追加モードを正しく実装していない(もしそのようなシステムがあれば) 場合でも、全てうまくいきます。 従って、実行する OS を flock()
が実装されているものに制限してもよいなら (これは実際のところそれほど大きな制限ではありません)、そうするべきです。
正しく追加を実装している(つまり Win32 ではない)システムを 利用しているとわかっている場合のみ、以前の答えのコードから seek()
を 省略できます。
追加モードを正しく実装している OS とファイルシステム(例えば最近の Unix の ローカルファイルシステム)でのみ実行されることがわかっていて、 ファイルをブロックバッファモードのままに維持していて、 手動でのバッファのフラッシュの間にバッファがいっぱいになるほどの 書き込みを行わない場合は、それぞれのバッファは他からの出力が混ざることなく、 ファイルの末尾にひとつの塊で書き込まれることがほとんど保証されます write(2)
システムコールの単なるラッパである syswrite()
を使うことも できます。
未だに、理論的にはシステムレベルの write()
操作が終了前にシグナルが割り込む 可能性が少しあります。 STDIO の実装によっては、バッファが空からスタートしても、複数のシステム レベルの write()
を呼び出すものがある可能性があります。 この確率を 0 にしているシステムもあるかもしれませんし、 システムの STDIO ではなく :perlio
を使うときには関係ありません。
単にバイナリにパッチをあてたいというのなら、多くの場合は以下のように 単純にできます:
perl -i -pe 's{window manager}{window mangler}g' /usr/bin/emacs
もし固定サイズのレコードを持っているのなら、以下のようにして行うことも できます:
$RECSIZE = 220; # size of record, in bytes
$recno = 37; # which record to update
open my $fh, '+<', 'somewhere' or die "can't update somewhere: $!";
seek $fh, $recno * $RECSIZE, 0;
read $fh, $record, $RECSIZE == $RECSIZE or die "can't read record $recno: $!";
# munge the record
seek $fh, -$RECSIZE, 1;
print $fh $record;
close $fh;
ロックとエラーチェックは読者の練習として残してあります。 これらを行うことを忘れてはいけません。 さもなくばとても後悔することになるでしょう。
そのファイルが最後に読み込まれ、書き出され、そのメタデータ(所有者など)が 変更された時刻を取得したいのであれば、perlfuncに説明がある -A, -M, -C といったファイルテスト演算子を使います。 これらはファイルの年齢(あなたのプログラムの実行開始に対するもの)を 浮動小数点数で表現された日数で取得します。 プラットフォームによってはこれら全ては利用できないものもあります。 紀元からの経過秒数による“生の”時間を得るためには、 stat関数を呼び出して、その値をlocaltime()、gmtime()、POSIX::strftime()を 使って人が読めるような形へ変換します。
例を挙げましょう:
my $write_secs = (stat($file))[9];
printf "file %s updated at %s\n", $file,
scalar localtime($write_secs);
もっと読みやすいものがお好みなら、File::statモジュールを使います (これは5.004以降の標準配布キットに含まれています)。
# error checking left as an exercise for reader.
use File::stat;
use Time::localtime;
my $date_string = ctime(stat($file)->mtime);
print "file $file updated at $date_string\n";
POSIX::strftime()アプローチは理論的にはロカール非依存で、 使う価値があります。詳しくはperllocaleを参照してください。
"utime" in perlfuncで説明されているutime()という関数を使います。 例として、引数の最初のファイルのread and write 時刻を読んで、 残りのファイルにその時刻を設定する小さなプログラムを挙げましょう。
if (@ARGV < 2) {
die "usage: cptimes timestamp_file other_files ...\n";
}
my $timestamp = shift;
my($atime, $mtime) = (stat($timestamp))[8,9];
utime $atime, $mtime, @ARGV;
例によって、エラーチェックは読者の練習として残してあります。
utime の perldoc には、既にある ファイルに対する touch(1) と同じ 効果のある例があります。
予測されたレベルの制度でファイルの時刻を保存する能力が制限された ファイルシステムもあります。 例えば、FAT と HPFS は 2 秒よりも細かい精度でファイルの作成時刻を 設定できません。 これはファイルシステムの制限であり、utime() での問題でありません。
一つのファイルハンドルを複数の出力ファイルハンドルに接続するには、 IO::Tee や Tie::FileHandle::Multiplex のモジュールが使えます。
これをする必要があるのが一回だけなら、以下のようにしてできます:
for my $fh (FH1, FH2, FH3) { print $fh "whatever\n" }
Perl においてファイルの全ての行を処理するための慣例的な手法は 1 行ずつ読みこむことです:
open my $input, '<', $file or die "can't open $file: $!";
while (<$input>) {
chomp;
# do something with $_
}
close $input or die "can't close $file: $!";
これはファイル全体を行の配列として読みこんでから1行ずつ処理するという (ほとんど常に、でないのなら)しばしば誤った手法よりも 遥かに効率的です。 それでもファイル全体を読み込みたいなら以下のようにします:
my @lines = <INPUT>;
本当に全てを一度に読み込む必要があるのかを良く考えるべきです。 これは単にスケールの問題ではありません。
CPAN にある File::Map モジュールでファイルを "mmap" した場合、 実際にメモリに保管することなく仮想的にファイル全体を文字列に読み込めます:
use File::Map qw(map_file);
map_file my $string, $filename;
一度マッピングされると、$string
を他の文字列と同じように扱えます。 データを読み込む必要性がないので、mmap によってとても速くなり メモリ消費を増やさないようにもできます。
標準の Tie::File
モジュールか DB_File
モジュールの $DB_RECNO
バインディングの方がより好ましいと考えるかもしれません。 これは配列とファイルを結び付けるので、配列にアクセスすると、実際には ファイルの対応する行にアクセスすることになります。
ファイル全体を読み込みたいなら、これを単純で効果的な 1 ステップで行うための、 File::Slurp
モジュールが使えます。
use File::Slurp;
my $all_of_it = read_file($filename); # entire file in scalar
my @all_lines = read_file($filename); # one line per element
または以下のようにファイルハンドルの内容全体を一つのスカラ変数に 読み込むことができます:
my $var;
{
local $/;
open my $fh, '<', $file or die "can't open $file: $!";
$var = <$fh>;
}
これは一時的にレコードセパレータを未定義にし、ブロックを出るときに 自動的にファイルをクローズします。ファイルが既にオープンしているなら、 単に以下のようにします:
my $var = do { local $/; <$fh> };
open
を削除するためにローカル化された @ARGV
も使えます:
my $var = do { local( @ARGV, $/ ) = $file; <> };
通常のファイルの場合は read
関数も使えます。
read( $fh, $var, -s $fh );
3 番目の引数は $fh
ファイルハンドルのデータのバイトサイズをテストし、 そのバイト数だけバッファ $var
に読み込みます。
$/
という変数を使います(詳しくはperlvarを参照してください)。 たとえば ("abc\n\n\n\ndef"
で三つではなく二つのパラグラフを受 け取る、つまり空のパラグラフを除去するには""
を設定します。空 のパラグラフを受け付けるには"\n\n"
を設定します。
空行はブランクを持っていてはいけないということに注意しましょう。 このため、"fred\n \nstuff\n\n"
は一つのパラグラフですが "fred\n\nstuff\n\n"
は二つのパラグラフです。
ほとんどのファイルハンドルに対してはgetc()
という組み込み関数を 使うことができますが、これはターミナルデバイスに対してはうまくいきません。 STDIN に対しては、CPAN にある Term::ReadKey を使うか "getc" in perlfuncにあるサンプルコードを使います。
あなたの使っているシステムがPOSIXをサポートしているのなら、echo を オフにしているのを気をつけながら以下のようなコードでできます。
#!/usr/bin/perl -w
use strict;
$| = 1;
for (1..4) {
print "gimme: ";
my $got = getone();
print "--> $got\n";
}
exit;
BEGIN {
use POSIX qw(:termios_h);
my ($term, $oterm, $echo, $noecho, $fd_stdin);
my $fd_stdin = fileno(STDIN);
$term = POSIX::Termios->new();
$term->getattr($fd_stdin);
$oterm = $term->getlflag();
$echo = ECHO | ECHOK | ICANON;
$noecho = $oterm & ~$echo;
sub cbreak {
$term->setlflag($noecho);
$term->setcc(VTIME, 1);
$term->setattr($fd_stdin, TCSANOW);
}
sub cooked {
$term->setlflag($oterm);
$term->setcc(VTIME, 0);
$term->setattr($fd_stdin, TCSANOW);
}
sub getone {
my $key = '';
cbreak();
sysread(STDIN, $key, 1);
cooked();
return $key;
}
}
END { cooked() }
CPAN にある Term::ReadKey モジュールならもっと簡単に使えます。 最新のバージョンでは non-portable システムのサポートも含まれています:
use Term::ReadKey;
open my $tty, '<', '/dev/tty';
print "Gimme a char: ";
ReadMode "raw";
my $key = ReadKey 0, $tty;
ReadMode "normal";
printf "\nYou said %s, char number %03d\n",
$key, ord $key;
あなたがすべき第一のことは、CPAN にある Term::ReadKey を入手することです。 今では閉鎖的な独占システム (オープンなシステムではない、POSIX でもなく、UNIX でもないような…)に 対する限定的なサポートさえあります。
comp.unix.* の Frequently Asked Questions(しばしば尋ねられる質問)リストで、 同じようなものをチェックすべきでしょう: この答えは本質的に同じものです。 つまり、非常にシステム依存なものです。 以下に挙げたのは、BSDシステムで動作する解決策の一つです。
sub key_ready {
my($rin, $nfd);
vec($rin, fileno(STDIN), 1) = 1;
return $nfd = select($rin,undef,undef,0);
}
どの位のキャラクターが待っているのかを知りたいのであれば、 FIONREAD ioctl 呼び出しを使うことができます。 h2phツールは C のインクルードファイルを Perl に変換するもので、その結果は require
によって呼び出すことが可能です。 FIONREAD は sys/ioctl.ph にある関数として定義されます。
require 'sys/ioctl.ph';
$size = pack("L", 0);
ioctl(FH, FIONREAD(), $size) or die "Couldn't call ioctl: $!\n";
$size = unpack("L", $size);
h2ph がインストールされていないか、うまく動作しなかったのであれば、 以下のようにできます。
% grep FIONREAD /usr/include/*/*
/usr/include/asm/ioctls.h:#define FIONREAD 0x541B
あるいはエディターの王様を使って小さな C プログラムを書きます:
% cat > fionread.c
#include <sys/ioctl.h>
main() {
printf("%#08x\n", FIONREAD);
}
^D
% cc -o fionread fionread.c
% ./fionread
0x4004667f
その後でその値をハードコードしてやって、あなたの仕事を引き継ぐ人のための 練習として移植をさぼります。
$FIONREAD = 0x4004667f; # XXX: opsys dependent
$size = pack("L", 0);
ioctl(FH, $FIONREAD, $size) or die "Couldn't call ioctl: $!\n";
$size = unpack("L", $size);
FIONREAD はストリームに接続されたファイルハンドルを要求します。 これはソケット、パイプ、あるいは tty デバイスではうまく動作しますが、 ファイルに対してはうまく行きません。
tail -f
をするには?まず最初に以下のものを試してみてください
seek(GWFILE, 0, 1);
seek(GWFILE, 0, 1)
という文はカレントの位置を変更しませんが、 そのファイルハンドルにおける end-fo-file 状態を解除します。 このため、次に <GWFILE>
とすると Perl は再度何かを読もうとするのです。
このやり方がうまくいかない(これはあなたの使っている stdio が実装している 機能に依存しています)のなら、以下のようにする必要があるでしょう:
for (;;) {
for ($curpos = tell(GWFILE); <GWFILE>; $curpos = tell(GWFILE)) {
# search for some stuff and put it into files
}
# sleep for a while
seek(GWFILE, $curpos, 0); # seek to where we had been
}
これでもうまく行かなければ、IO::Handle
の clearerr
メソッドを 検討してください; これはハンドルのエラーと end-of-file 状態をリセットします。
CPANには File::Tail
モジュールがあります。
"open" in perlfunc を見れば、それを行うための open() の呼び出し方が 何通りもあることに気がつくでしょう。例を挙げます:
open my $log, '>>', '/foo/logfile';
open STDERR, '>&LOG';
あるいは、リテラルの数値記述子を使います:
my $fd = $ENV{MHCONTEXTFD};
open $mhcontext, "<&=$fd"; # like fdopen(3S)
"<&STDIN" はコピーを作成し、"<&=STDIN" がエイリアスを作成するということに 注意してください。 これはつまり、あなたがエイリアスが作成されたファイルハンドルを クローズすると、エイリアスはすべてアクセスできなくなります。 これはコピーの場合にはそうはなりません。
エラーチェックはいつものように読者の練習のために残してあります。
もし、何らかの理由で、ファイルハンドルではなくファイル記述子を持っている場合 (おそらく POSIX::open
を使ったのでしょう)、POSIX
モジュールの close()
関数が使えます:
use POSIX ();
POSIX::close( $fd );
Perl の close()
関数は、先の例にあった MHCNOTEXT
のように数値 記述子を使って dup したものでさえも含めて、 Perl 自身がオープンしたものに対して使うことができますから、 その必要はほとんどないはずです。 しかし本当にそうする必要があるのなら、以下のようにできるでしょう:
require 'sys/syscall.ph';
my $rc = syscall(&SYS_close, $fd + 0); # must force numeric
die "can't sysclose $fd: $!" unless $rc == -1;
あるいは、単に open()
の fdopen(3S) 機能を使います:
{
open my( $fh ), "<&=$fd" or die "Cannot reopen fd=$fd: $!";
close $fh;
}
おーっと! ファイル名にタブや改ページを入れてしまいましたね! "like\this"のようにダブルクォートで括られた文字列の中では、 バックスラッシュはエスケープキャラクターであるということを思い出してください。 エスケープキャラクター全てのリストは "Quote and Quote-like Operators" in perlopにあります。 当然のことでしょうが、あなたの使っているDOSのファイルシステムでは "c:(tab)emp(formfeed)oo" とか "c:(tab)emp(formfeed)oo.exe" なんて名前のファイルはできませんよね。
文字列を括るのにシングルクォートを使うか、もしくは(こちらが好ましい) スラッシュを使ってください。 全てのDOSおよびWindowsは、MS-DOS 2.0以降、パス中にある/
と \
を 同じに扱いますから、あなたはPerlを壊すことなく使えます。 もしくは POSIXシェル、ANSI CとC++、awk、tcl、Java、Python を考慮してください。 POSIXパスはより移植性に富んでいます。
非 UNIX システムに対する移植であっても、Perl の glob 関数は UNIX の標準的な グロブの振る舞いに従うからです。 全ての(隠し属性でない)ファイルを得るには glob("*")
とする必要があります。 これは glob() の移植性を高めます。 あなたの使っている Perl が独自のグロブ関数をサポートしているかもしれません。 詳しくはドキュメントを参照してください。
-i
clobberはファイルをプロテクトするの? これは Perl のバグじゃないの?http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz の "Far More Than You Ever Wanted To Know" にあるfile-dir-perms の 記事が教育的、かつ懇切丁寧にこの問題を説明しています。
簡単なまとめ: あなたの使っているファイルシステムがどのように動作しているのかを 考えてください。 ファイルに対するパーミッションはそのファイルにあるデータに何が できるかということを表しています。 ディレクトリに対するパーミッションは、そのディレクトリにあるファイルリストに 対して何ができるのかということを表しています。 ファイルを削除したとき、そのファイルに対する名前がディレクトリから 取り除かれます(したがってこの操作はファイルに対するパーミッションでは なく、ディレクトリに対するパーミッションに依存しているのです)。 ファイルに対して書き込みを行おうとすると、ファイルに対するパーミッションが それができるかどうかを決定します。
ファイルをデータベースに読み込ませたり、ファイルの行数のインデックスを 予め作っておく以外では、これをするためには 2 種類の方法があります:
以下に示すのはらくだ本にあったリザーバサンプリングアルゴリズムです:
srand;
rand($.) < 1 && ($line = $_) while <>;
これは、ファイル全体を読み込んで処理するやり方に比べて使用する空間の 大きさにおいて明らかなアドバンテージがあります。 この手法の証明は Donald E. Knuth の The Art of Computer Programming, Volume 2, Section 3.4.2 に あります。
このアルゴリズムの関数を提供する File::Random
モジュールが使えます:
use File::Random qw/random_line/;
my $line = random_line($filename);
もう一つの方法は Tie::File
モジュールを使うことで、これはファイル全体を 配列として扱います。 単にランダムに配列にアクセスしてください。
(brian d foy によって寄贈されました)
もし配列を表示したときに配列の要素の間に空白が付くなら、 おそらく配列をダブルクォートで展開しています:
my @animals = qw(camel llama alpaca vicuna);
print "animals are: @animals\n";
これは print
ではなくダブルクォートが行っています。 ダブルクォートコンテキストで配列を展開するときはいつでも、 Perl は要素を空白(正確にはデフォルトが空白の $"
)を使って 結合します:
animals are: camel llama alpaca vicuna
これは、配列を変数展開なしに表示するのとは異なります:
my @animals = qw(camel llama alpaca vicuna);
print "animals are: ", @animals, "\n";
今度は要素の間には空白はありません; @animals
の要素は単に print
へのリストの一部となっているからです:
animals are: camelllamaalpacavicuna
@array
の要素のそれぞれが改行で終わっているときにこのことに 気付いたかも知れません。 行毎に 1 要素を表示することを想定したけれども、最初の以外の行が インデントされることに気付きます:
this is a line
this is another line
this is the third line
追加の空白は配列の展開による物です。配列の要素の間に何も表示したくない 場合、ダブルクォートの中で配列を使わないでください。 使わなくても表示できます:
print @lines;
(brian d foy によって寄贈されました)
Perl と共に配布されている File::Find
モジュールが、ディレクトリ構造を 辿るための大変な作業の全てを行います。 これは Perl と共に配布されています。 単に、コールバックサブルーチンと、辿りたいディレクトリを指定して find
サブルーチンを呼び出します:
use File::Find;
find( \&wanted, @directories );
sub wanted {
# full path in $File::Find::name
# just filename in $_
... do whatever you want to do ...
}
CPAN からダウンロードできる File::Find::Closures
は、 File::Find
と共に使える、たくさんの「すぐに使える」サブルーチンを 提供します。
CPAN からダウンロードできる File::Finder
は、 find
コマンドラインユーティリティの文法により近い形の コールバックサブルーチンを作ることを手助けします:
use File::Find;
use File::Finder;
my $deep_dirs = File::Finder->depth->type('d')->ls->exec('rmdir','{}');
find( $deep_dirs->as_options, @places );
CPAN からダウンロードできる File::Find::Rule
モジュールは、 同じようなインターフェースを持ちますが、ファイルを辿ることもします:
use File::Find::Rule;
my @files = File::Find::Rule->file()
->name( '*.pm' )
->in( @INC );
(brian d foy によって寄贈されました)
もし、ディレクトリが空なら、Perl 組み込みの rmdir
が使えます。 もしディレクトリが空(ファイルもサブディレクトリもない)ではないなら、 自力で空にする(多くの作業が必要です)か、助けてくれるモジュールを 使う必要があります。
Perl と共に配布されている File::Path
モジュールには、大変な作業の全てを 代わりに処理してくれる remove_tree
があります:
use File::Path qw(remove_tree);
remove_tree( @directories );
File::Path
モジュールには、より古い rmtree
サブルーチンへの レガシーなインターフェースもあります。
(Shlomi Fish によって寄贈されました)
cp -R
と等価なこと (つまり、ディレクトリツリー全体を再帰的に コピーする) を移植性のある Perl でするには、自分で書くか、 File::Copy::Recursive のようなよい CPAN モジュールを見つける必要が あります。
Copyright (c) 1997-2010 Tom Christiansen, Nathan Torkington, and other authors as noted. All rights reserved.
This documentation is free; you can redistribute it and/or modify it under the same terms as Perl itself.
Irrespective of its distribution, all code examples here are in the public domain. You are permitted and encouraged to use this code and any derivatives thereof in your own programs for fun or for profit as you see fit. A simple comment in the code giving credit to the FAQ would be courteous but is not required.