15

私は通常、次のコードを使用してファイル内の行をループします。

open my $fh, '<', $file or die "Could not open file $file for reading: $!\n";
while ( my $line = <$fh> ) {
  ...
}

ただし、別の質問に答える際に、Evan Carrollは私の答えを編集し、私のwhileステートメントを次のように変更しました。

while ( defined( my $line = <$fh> ) ) {
  ...
}

彼の理論的根拠は、次の行がある場合0(最後の行である必要があり、そうでない場合はキャリッジリターンがあります)、while私のステートメント($lineに設定され"0"、からの戻り値)を使用すると、途中で終了するというものでした。したがって、割り当ては、"0"falseと評価される割り当てにもなります)。定義性をチェックすれば、この問題に遭遇することはありません。それは完全に理にかなっています。

だからやってみました。最後の行に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"呼び出すと同様の結果が得られるため、値は実際には文字列であることがわかります(唯一の違いはLENフィールドにあります-ファイルLENは80ですが、スカラーからのLENは8)です。Dump()"0"

それで、取引は何ですか?キャリッジリターンがないwhile()行だけをループに渡すと、ループが途中で終了しないのはなぜですか?"0"Evanのループは実際にはより防御的ですか、それともPerlは内部で何かおかしなことをしますか?つまり、これらのことを心配する必要はなく、while()実際にヒットしたときにのみ終了しますeofか?

4

3 に答える 3

18

なぜなら

 while (my $line = <$fh>) { ... }

実際にコンパイルすると

 while (defined( my $line = <$fh> ) ) { ... }

非常に古いバージョンの perl では必要だったかもしれませんが、今は必要ありません! これは、スクリプトで B::Deparse を実行するとわかります。

>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

だから、あなたはすでに行く準備ができています!

于 2010-09-22T22:02:11.313 に答える
13

ところで、これはperldoc perlopの I/O Operators セクションでカバーされています:

スカラーコンテキストでは、山括弧内のファイルハンドルを評価すると、そのファイルの次の行 (改行がある場合は含まれます)、またはファイルの終わりまたはエラーで「undef」が生成されます。$/ が "undef" (file-slurp モードとも呼ばれます) に設定され、ファイルが空の場合、最初は '' が返され、続いて "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 によって false として扱われる文字列値が line に含まれる問題を回避します。たとえば、末尾に改行がない "" または "0" です。そのような値がループを終了することを本当に意味する場合は、明示的にテストする必要があります。

while (($_ = <STDIN>) ne '0') { ... }
while (<STDIN>) { last unless $_; ... }

他のブール値のコンテキストでは、明示的な「定義済み」テストまたは比較なしの「<filehandle>」は、「use warnings」プラグマまたは -w コマンドライン スイッチ ($^W 変数) が有効な場合に警告を引き出します。

于 2010-09-22T22:17:59.767 に答える
1

while (my $line=<$fh>) { ... }getsの形式を考慮してコンパイルするのは正しいのですが、ループ内に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: $!";
}

これらが良い形の Perl だと言っているのではありません! 私はそれらが可能であると言っています。特に失敗 3、4、および 5。4 番と 5 番で Perl の警告が表示されない失敗に注意してください。最初の 2 つには独自の問題があります...

于 2010-09-23T02:17:06.633 に答える