これを Perl でどのように実装できるか疑問に思っていました:
while ( not the end of the files )
$var1 = read a line from file 1
$var2 = read a line from file 2
# operate on variables
end while
while
1 つのループで 2 つのファイルから一度に 1 行ずつ読み取る方法がわかりません。
Seems like you wrote your answer yourself, almost. Just check for eof
for both file handles, like so:
while (not eof $fh1 and not eof $fh2) {
my $var1 = <$fh1>;
my $var2 = <$fh2>;
# do stuff
}
More reading:
注: @zostay と @jm666 のコメントに応じて、回答を拡大しました。
この質問に対する効率的で明確かつ簡潔な答えを見つけるための最初のステップは、関連する変数が集約されるという考えから始まります。したがって、配列@fh
には、同時に読み取るファイルハンドルが含まれます。
次に、各ファイルハンドルから行を読み取り、演算子とmap<>
を組み合わせて使用して、それらを配列に格納できます。変換規則とリストを受け取り、別のリストを返します。したがって:map
my @lines = map scalar <$_>, @fh;
でファイルハンドルを取得し、それぞれから 1 行を読み取り (スカラー@fh
に注意)、それらの行を に配置します。の変形です。@lines
one-to-one
@fh
のドキュメントが<>
示す<>
ように、ファイルの終わりに達した場合、またはエラーがある場合は、未定義の値を返します。
ここで、すべてのファイルからの読み取りが成功したかどうかを確認する 1 つの方法は、定義された行数がファイルハンドルの数と同じかどうかを確認することです。grepは、特定の基準を満たすリストの要素を選択します。したがって
@fh == grep defined, my @lines = map <$_>, @fh;
のファイルハンドル@fh
の数が で定義された要素の数と同じかどうかをチェックし@lines
ます。ただし、@fh
この比較の両側に表示されることは確かに混乱を招く可能性があるため、未定義の要素がないことを確認する別の方法は次の@lines
とおりです。
0 == grep !defined, my @lines = map <$_>, @fh;
その条件をwhileループに入れたい場合は、次のように記述する必要があります。
while (0 == grep !defined, my @lines = map <$_>, @fh) {
一方、untilを使用する場合は、次のように単純に記述できます。
until (grep !defined, my @lines = map <$_>, @fh) {
これは、「readlines の少なくとも 1 つが未定義の値を返すまで、ループの本体を実行する」ことを意味します。
ここで、 Perlはeof
Ceof
とは異なることに注意してください。Perl のeof
ドキュメントには、次のように記載されています。
実用的なヒント: Perl で使用する必要はほとんどありません。
eof
入力演算子は通常undef
、データが不足したり、エラーが発生したりすると返されるためです。
eof
ループを毎回チェックすると、「この関数は実際に文字を読み取ってからそれを実行するungetc
」ため、ファイル IO が 2 倍になります。
私はほとんどの場合、自分のコードで自己完結型の実行可能な例を示しています。以下では、システムに存在する特定のファイルに依存したくなかったので、常に利用可能なDATA
ハンドルSTDIN
を使用しています。関数を使用するのとは対照的にeof
、この方法を使用する場合、どこから読み取っているのかを気にする必要はありません。気にするのは、いずれかのファイルの readline が未定義の値を返したかどうかだけです。また、任意の数のファイルハンドルで使用できます。また、実際にはファイルハンドルを配列に入れていませんが、前述したように、関連する変数は集合体に属しているため、次のようなものを入力していることに気付いた場合
my $var1 = <$fh1>;
my $var2 = <$fh2>;
ファイルハンドルを格納するために配列を使用する必要があったことを認識してください。
#!/usr/bin/env perl
use strict; use warnings;
my @fh = (\*DATA, \*STDIN);
until (grep !defined, my @lines = map scalar <$_>, @fh) {
print for @lines;
}
__DATA__
one
two
three
STDIN
このサンプル スクリプトは、行DATA
がなくなると入力の要求を停止します。スクリプトに末尾の空白行がない場合は、次のように入力する必要があります三スクリプトが終了する 4 行前。
ここで、どのファイルハンドルが最後に達したかを知りたい場合は、次のようなものを使用するように切り替えます:
#!/usr/bin/env perl
use strict; use warnings;
my @fh = (\*DATA, \*STDIN);
while (1) {
my @lines = map scalar <$_>, @fh;
if (my @eof = grep !defined($lines[$_]), 0 .. $#fh) {
warn "Could not read from filehandle(s) '@eof'";
last;
}
print for @lines;
}
__DATA__
one
two
three
上記のループは、いずれかのファイルが使い果たされると停止するように設計されています。一方、すべてのファイルがなくなるまでループを実行したい場合もあります。その場合、次を使用します。
while (grep defined, my @lines = map scalar <$_>, @fh) {
明示的なeof()
チェックを行わない別の簡単な解決策は、次のようになります。
while (defined(my $var1 = <$fh1>) and defined(my $var2 = <$fh2>)) {
# do stuff
}
これは、ファイルの最後にいる場合にのみ if &を<>
返すという事実を使用しています。undef