- ベストアンサー
範囲演算子と文字列マッチングを組み合わせたときの解除方法
Windows-XP上でActivePerl/5.8.8を利用しています。 テキストファイルなどで、ある文字列が現れた行から、ある文字列が現れるまで、 ということを判定させるときに、範囲演算子が使えるということを知りました。 while(<>){ chomp; if(/^START$/ .. /^END$/){ # 範囲指定 ・・・ STARTの行から、ENDの行までこのブロックに入る } } 1ファイルに対してだけ処理させるときは上手く行くのですが、 連続して複数のファイルを処理させようとすると、 2番目のファイルからは、開始条件(/^START$/)が既に成立したと 判断されてしまうようで、該当行が現れていないのに、ifブロックに 入ってしまいます。 foreach(@ARGV){ # 複数ファイルに対して処理させる open(FH,$_) || die; while(<FH>){ chomp; if(/^START$/ .. /^END$/){ # 範囲指定 ・・・ 1つ目のファイルではSTARTの行から、ENDの行まででこのブロックに入るが、 2つ目のファイルではSTARTの行が現れないうちからこのブロックに入ってしまう。 } } close(FH); } これを2つ目のファイル以降も、範囲指定の開始条件が成立していない 状態から処理させるためには、どのようにすれば良いでしょうか。 よろしくお願い致します。
- みんなの回答 (9)
- 専門家の回答
質問者が選んだベストアンサー
No.6 です。最後の行が END の場合を見落としていました。if (/^2009/ or eof()) { $end = '.*'; last; } の行を次のように変更します。 if (/^2009/ or (eof() and $_ !~ /^END$/)) { $end = '.*'; last; } > それだと > if (/^START$/ ... /^$end$/) { > のように点は 3つにしないと不都合ではないでしょうか>#6. if (/^START$/ .. /^$end$/) { if ($end eq '.*') { $end = 'END'; redo; } ... if (/^2009/ or (eof() and $_ !~ /^END$/)) { $end = '.*'; last; } } 上のコードは、前のファイルで範囲演算子が真の状態で終了した場合に、次のファイルの1行目で 範囲演算子をクリアして $end を元に戻す仕組みです。たとえ次のファイルの1行目が START で あっても範囲演算子が真の間は左オペランドは評価されないので問題がないと思いますがどうでしょうか。
その他の回答 (8)
- Tacosan
- ベストアンサー率23% (3656/15482)
(じっとコードを追ってみる...) ああ, その通りです>#8. 両方を同時に評価するパスがあると思いこんでました... eof() は OK でしたっけ? eof の方が安全?
お礼
お礼が遅くなりすみませんでした。 ご回答ありがとうございました。
- Tacosan
- ベストアンサー率23% (3656/15482)
それだと if (/^START$/ ... /^$end$/) { のように点は 3つにしないと不都合ではないでしょうか>#6. 「..」のままだと左オペランドにマッチしたときに右オペランドもチェックしてしまい, .* では必ずマッチしてしまうのでこの if の中には入らないと思います. あるいは「何にもマッチしない正規表現」(?!) を使うか.
お礼
しばらく「点が3つ」の仕様が分かりませんでした。 左オペランドが真となった後、次の行に移ってから右オペランドの評価を行う。 ということなのですね。 今回の例の場合は、間違いなく「点が3つ」の方が意図に合っていました。 ありがとうございました。 (お礼が遅くなり、すみませんでした)
- kumoz
- ベストアンサー率64% (120/185)
>(1)あるファイルには「END」が書かれていない場合がある。 >(2)START~ENDの間に別のwhileループ脱出条件があって、END判定が成立しないまま次のファイルへ移ってしまう。 範囲演算子の右オペランドを変数にして、while ループの中で操作してはどうでしょうか。 $end = 'END'; foreach (@ARGV) { open(FH, $_) || die; while (<FH>) { chomp; if (/^START$/ .. /^$end$/) { if ($end eq '.*') { $end = 'END'; redo; } ... if (/^2009/ or eof()) { $end = '.*'; last; } } } }
- Tacosan
- ベストアンサー率23% (3656/15482)
おっと, ファイルハンドル ARGV をオープンし忘れた. while (@ARGV) { $ARGV = shift @ARGV; open ARGV, '<', $ARGV; my $cond = eval 'sub { /^START$/ .. /^END$/; }'; while (<ARGV>) { if (&$cond) { なんかする last if なんか } }
- Tacosan
- ベストアンサー率23% (3656/15482)
ざっと調べた感じでは, 残念ながら単純にはできなさそうです. ただし, さすがに Perl なので黒魔術を使えば何とかなるかもしれません. 例えば while (@ARGV) { $ARGV = shift @ARGV; my $cond = eval 'sub { /^START$/ .. /^END$/; }'; while (<ARGV>) { if (&$cond) { なんかする last if なんか } } で回避できているかも.
お礼
お礼が遅くなりました。 色々と”力技”を使えばできそうでしたが、単純な方法としては、なさそうですね。 ありがとうございました。
- _--_1l1_1_
- ベストアンサー率67% (102/152)
上手くいくようですが。もしかして質問の理解がまちがってます? #!/usr/bin/perl use strict; use warnings; for (@ARGV) { open my $fh, '<', $_ or die "$!:$_"; while (<$fh>) { chomp; if ( /^START$/ .. /^END$/ ) { print "match: "; } else { print "not match: "; } print $_, "\n"; } close $fh or die "$!:$_"; } $ cat tmp2.txt a b START c d e END f g $ cat tmp3.txt 1 2 START 3 4 5 END 6 7 $ perl foo.pl tmp2.txt tmp3.txt not match: a not match: b match: START match: c match: d match: e match: END not match: f not match: g not match: 1 not match: 2 match: START match: 3 match: 4 match: 5 match: END not match: 6 not match: 7
お礼
実際に動作をご確認頂いて恐縮です。ありがとうございます。 質問が分かりづらくて申し訳ありません。 #1,#2の方へのコメントとして書かせて頂きましたが、 (1)あるファイルには「END」が書かれていない場合がある。 (2)START~ENDの間に別のwhileループ脱出条件があって、END判定が成立しないまま次のファイルへ移ってしまう。 の2つのパターンに対応したいと思っています。
- Tacosan
- ベストアンサー率23% (3656/15482)
あとついでですが, 実は while (<>) { chomp; if(/^START$/ .. /^END$/ || eof){ なんかする } } でいいのかもしれない.
補足
何度もご教示ありがとうございます。 はい。それで、この if(/^START$/ .. /^END$/ || eof){ ・・・・ } ブロックの中で、ある別の条件が成立したために、 lastで、whileを抜けさせた場合・・・例えば、 while (<>) { chomp; if(/^START$/ .. /^END$/ || eof){ なんかする。 last if(/^2009/); } } のようにしていた場合に、次のファイルの処理に入ると問題の状況になるようです。 lastでwhileを抜けた後、 /^START$/ .. /^END$/ の判定をやめなさい。 という命令が出来ればいいのですが、いかがでしょうか。
- Tacosan
- ベストアンサー率23% (3656/15482)
実はオペランドは「式」でかまわないので, /^START$/ .. /^END$/ || eof でよかったりして
お礼
こういう書き方が出来るのですね。 ご指摘の通りで、終了の文字ENDが見つからないまま eofを迎えたときに問題の状況に陥いるのでした。 ご教示、ありがとうございました。
お礼
すみません、条件データが良くなかったので、次のように訂正します。 -tmp1.txt aaa START bbb ccc -tmp2.txt xxx START yyy 2009 zzz END vvv -tmp3.txt 2009 START 2008 2007 END 2006 2005 2つの動作検証結果でご報告します。 ■1■ プログラム1つ目 foreach my $filename (@ARGV) { open my $FH, '<', $filename or die "$!:$filename"; while (<$FH>) { chomp; if( /^START$/ .. /^END$/ ) { print "match $filename: $_\n" unless(/^START$/ || /^END$/); last if(/^2009/); } } close $FH or die "$!:$filename"; } ■■1の実行結果 c:\>hanni.pl tmp1.txt tmp2.txt tmp3.txt match tmp1.txt: bbb ⇒ OK match tmp1.txt: ccc ⇒ OK match tmp2.txt: xxx ← NG:STARTのタグの前にある行 match tmp2.txt: yyy ⇒ OK(結果的に) match tmp2.txt: 2009 ⇒ OK & 途中中断条件・・・ match tmp3.txt: 2009 ← NG:STARTの前にある条件なのに終了してしまった。 ■2■ プログラム2つ目(kumozさんのを少しだけ書き換えました) my $end = 'END'; foreach my $filename (@ARGV) { open my $FH, '<', $filename or die "$!:$filename"; L2: while (<$FH>) { chomp; if( /^START$/ .. /^$end$/ ) { if ($end eq '.*'){ $end = 'END'; redo L2; } print "match $filename: $_\n" unless(/^START$/ || /^$end$/); if(/^2009/ || eof){ $end = '.*'; last L2; } } if(eof){ $end = '.*'; last L2; } } close $FH or die "$!:$filename"; } ■■2の実行結果 c:\>hanni2.pl tmp1.txt tmp2.txt tmp3.txt match tmp1.txt: bbb match tmp1.txt: ccc match tmp2.txt: yyy match tmp2.txt: 2009 match tmp3.txt: 2008 match tmp3.txt: 2007 これが今回、期待した結果で、バッチリでした。ありがとうございました。 お礼が遅くなり、大変申し訳ありませんでした。
補足
3つのデータファイルを以下のように用意したとして、各種の動作結果をご報告いたします。 tmp1.txt ----------------------- aaa START bbb ccc END ddd 2009 eee -------------------------------- tmp2.txt ----------------------- xxx START yyy 2009 zzz END vvv -------------------------------- tmp3.txt ----------------------- 111 START 222 333 END 444 555 --------------------------------