Perlを使用したファイル内の線をループする最も防御的な方法は何ですか?
-
04-10-2019 - |
質問
私は通常、次のコードを使用してファイル内の線をループします。
open my $fh, '<', $file or die "Could not open file $file for reading: $!\n";
while ( my $line = <$fh> ) {
...
}
でも、 別の質問に答える際, エヴァン・キャロル 私の答えを編集して、私を変えました while
声明:
while ( defined( my $line = <$fh> ) ) {
...
}
彼の理論的根拠は、もしあなたがその線を持っているなら 0
(それは最後の行でなければなりません、そうでなければそれはキャリッジを返します)そしてあなたの while
あなたが私の声明を使用した場合、早めに終了します($line
に設定されます "0"
, 、そして割り当てからの返品値も "0"
これは虚偽に評価されます)。定義さを確認すると、この問題に遭遇しません。それは完全に理にかなっています。
だから私はそれを試しました。最後の行があるテキストファイルを作成しました 0
キャリッジはそれに戻りません。ループを通り抜けて実行しましたが、ループは時期尚早に終了しませんでした。
それから私は、「ああ、多分価値は実際にはないかもしれない 0
, 、多分そこに何かを台無しにしている何かがあります!」それで私は使用しました Dump()
から Devel::Peek
そして、これは私に与えたものです:
SV = PV(0x635088) at 0x92f0e8
REFCNT = 1
FLAGS = (PADMY,POK,pPOK)
PV = 0X962600 "0"\0
CUR = 1
LEN = 80
それは私に値が実際に文字列であることを教えているようです "0"
, 、私が電話した場合、私が同様の結果を得るので Dump()
スカラーで明示的に設定しました "0"
(唯一の違いはレンフィールドにあります - ファイルからレンは80ですが、スカラーからレンは8です)。
それで、取引は何ですか?なぜ私はしません while()
ループは、私がそれを渡すと、時期尚早に終了します。 "0"
キャリッジリターンがありませんか?エヴァンのループは実際にはより防御的ですか、それともペルルは内部的に何かクレイジーなことをしますか? while()
実際には、ヒットしたときにのみ終了します eof
?
解決
なぜなら
while (my $line = <$fh>) { ... }
実際にコンパイルします
while (defined( my $line = <$fh> ) ) { ... }
非常に古いバージョンのPerlで必要だったかもしれませんが、これ以上ではありませんでした!これは、b :: deparse on your scriptの実行から見ることができます。
>perl -MO=Deparse
open my $fh, '<', $file or die "Could not open file $file for reading: $!\n";
while ( my $line = <$fh> ) {
...
}
^D
die "Could not open file $file for reading: $!\n" unless open my $fh, '<', $file;
while (defined(my $line = <$fh>)) {
do {
die 'Unimplemented'
};
}
- syntax OK
だからあなたはもう行きます!
他のヒント
ところで、これはのI/O演算子セクションでカバーされています perldoc perlop:
スカラーのコンテキストでは、アングルブラケットのファイルハンドルを評価すると、そのファイル(新しいライン(存在する場合、含まれている場合)、またはエラー時に「UNDEF」から次の行が得られます。 $/が「undef」(ファイルスループモードと呼ばれることもある)に設定され、ファイルが空になると、最初に返され、その後「undef」が続きます。
通常、返された値を変数に割り当てる必要がありますが、自動割り当てが発生する状況が1つあります。入力シンボルが「while」ステートメントの条件内の唯一のものである場合にのみ(「for(;;)」ループとして偽装されていても)、値はグローバル変数$ _に自動的に割り当てられ、何でも破壊されます以前はありました。 (これはあなたにとって奇妙なことのように思えるかもしれませんが、あなたが書いたほぼすべてのPerlスクリプトでコンストラクトを使用します。)$ _変数は暗黙的にローカライズされていません。 「ローカル$ _;」を入れる必要があります。ループの前にそれが起こることを望むなら。
次の行は同等です。
while (defined($_ = <STDIN>)) { print; } while ($_ = <STDIN>) { print; } while (<STDIN>) { print; } for (;<STDIN>;) { print; } print while defined($_ = <STDIN>); print while ($_ = <STDIN>); print while <STDIN>;
これも同様に動作しますが、$ _を回避します。
while (my $line = <STDIN>) { print $line }
これらのループコンストラクトでは、割り当てられた値(割り当てが自動または明示的であるかどうか)がテストされて、それが定義されているかどうかを確認します。定義されたテストでは、ラインがPerlによって偽として扱われる文字列値を持つ問題を回避します。たとえば、「 ""またはa "0"が後続の新しいラインがない場合があります。そのような値がループを終了することを本当に意味する場合は、それらを明示的にテストする必要があります。
while (($_ = <STDIN>) ne '0') { ... } while (<STDIN>) { last unless $_; ... }
他のブールの文脈では、u003Cfilehandle> 「明示された」テストがない場合、または比較は、「警告を使用する」プラグマまたは-Wコマンドラインスイッチ($^w変数)が有効になった場合、警告を引き出します。
の形が正しいですが while (my $line=<$fh>) { ... }
取得 編集済み に while (defined( my $line = <$fh> ) ) { ... }
明示的なものを持っていない場合、値「0」の合法的な読み取りが誤って解釈される場合があります defined
ループで、またはの返品をテストします <>
.
ここにいくつかの例があります:
#!/usr/bin/perl
use strict; use warnings;
my $str = join "", map { "$_\n" } -10..10;
$str.="0";
my $sep='=' x 10;
my ($fh, $line);
open $fh, '<', \$str or
die "could not open in-memory file: $!";
print "$sep Should print:\n$str\n$sep\n";
#Failure 1:
print 'while ($line=chomp_ln()) { print "$line\n"; }:',
"\n";
while ($line=chomp_ln()) { print "$line\n"; } #fails on "0"
rewind();
print "$sep\n";
#Failure 2:
print 'while ($line=trim_ln()) { print "$line\n"; }',"\n";
while ($line=trim_ln()) { print "$line\n"; } #fails on "0"
print "$sep\n";
last_char();
#Failure 3:
# fails on last line of "0"
print 'if(my $l=<$fh>) { print "$l\n" }', "\n";
if(my $l=<$fh>) { print "$l\n" }
print "$sep\n";
last_char();
#Failure 4 and no Perl warning:
print 'print "$_\n" if <$fh>;',"\n";
print "$_\n" if <$fh>; #fails to print;
print "$sep\n";
last_char();
#Failure 5
# fails on last line of "0" with no Perl warning
print 'if($line=<$fh>) { print $line; }', "\n";
if($line=<$fh>) {
print $line;
} else {
print "READ ERROR: That was supposed to be the last line!\n";
}
print "BUT, line read really was: \"$line\"", "\n\n";
sub chomp_ln {
# if I have "warnings", Perl says:
# Value of <HANDLE> construct can be "0"; test with defined()
if($line=<$fh>) {
chomp $line ;
return $line;
}
return undef;
}
sub trim_ln {
# if I have "warnings", Perl says:
# Value of <HANDLE> construct can be "0"; test with defined()
if (my $line=<$fh>) {
$line =~ s/^\s+//;
$line =~ s/\s+$//;
return $line;
}
return undef;
}
sub rewind {
seek ($fh, 0, 0) or
die "Cannot seek on in-memory file: $!";
}
sub last_char {
seek($fh, -1, 2) or
die "Cannot seek on in-memory file: $!";
}
私はこれらがパールの良い形だと言っているのではありません! 私はそれらが可能だと言っています。特に障害3,4と5。番号4と5にPerl警告なしの障害に注意してください。最初の2つには独自の問題があります...