プロが教えるわが家の防犯対策術!

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つ目のファイル以降も、範囲指定の開始条件が成立していない
状態から処理させるためには、どのようにすれば良いでしょうか。
よろしくお願い致します。

「範囲演算子と文字列マッチングを組み合わせ」の質問画像

A 回答 (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 で
あっても範囲演算子が真の間は左オペランドは評価されないので問題がないと思いますがどうでしょうか。

この回答への補足

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
--------------------------------

補足日時:2009/12/07 15:21
    • good
    • 0
この回答へのお礼

すみません、条件データが良くなかったので、次のように訂正します。
-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

これが今回、期待した結果で、バッチリでした。ありがとうございました。
お礼が遅くなり、大変申し訳ありませんでした。

お礼日時:2009/12/07 17:53

(じっとコードを追ってみる...) ああ, その通りです>#8. 両方を同時に評価するパスがあると思いこんでました... eof(

) は OK でしたっけ? eof の方が安全?
    • good
    • 0
この回答へのお礼

お礼が遅くなりすみませんでした。
ご回答ありがとうございました。

お礼日時:2009/12/07 13:35

それだと


if (/^START$/ ... /^$end$/) {
のように点は 3つにしないと不都合ではないでしょうか>#6. 「..」のままだと左オペランドにマッチしたときに右オペランドもチェックしてしまい, .* では必ずマッチしてしまうのでこの if の中には入らないと思います.
あるいは「何にもマッチしない正規表現」(?!) を使うか.
    • good
    • 0
この回答へのお礼

しばらく「点が3つ」の仕様が分かりませんでした。
左オペランドが真となった後、次の行に移ってから右オペランドの評価を行う。
ということなのですね。
今回の例の場合は、間違いなく「点が3つ」の方が意図に合っていました。
ありがとうございました。
(お礼が遅くなり、すみませんでした)

お礼日時:2009/12/07 13:41

>(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; }
}
}
}
    • good
    • 0

おっと, ファイルハンドル ARGV をオープンし忘れた.


while (@ARGV) {
$ARGV = shift @ARGV;
open ARGV, '<', $ARGV;
my $cond = eval 'sub { /^START$/ .. /^END$/; }';
while (<ARGV>) {
if (&$cond) {
なんかする
last if なんか
}
}
    • good
    • 0

ざっと調べた感じでは, 残念ながら単純にはできなさそうです.


ただし, さすがに Perl なので黒魔術を使えば何とかなるかもしれません. 例えば
while (@ARGV) {
$ARGV = shift @ARGV;
my $cond = eval 'sub { /^START$/ .. /^END$/; }';
while (<ARGV>) {
if (&$cond) {
なんかする
last if なんか
}
}
で回避できているかも.
    • good
    • 0
この回答へのお礼

お礼が遅くなりました。
色々と”力技”を使えばできそうでしたが、単純な方法としては、なさそうですね。
ありがとうございました。

お礼日時:2009/12/07 13:43

上手くいくようですが。

もしかして質問の理解がまちがってます?
#!/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
    • good
    • 0
この回答へのお礼

実際に動作をご確認頂いて恐縮です。ありがとうございます。

質問が分かりづらくて申し訳ありません。
#1,#2の方へのコメントとして書かせて頂きましたが、

(1)あるファイルには「END」が書かれていない場合がある。
(2)START~ENDの間に別のwhileループ脱出条件があって、END判定が成立しないまま次のファイルへ移ってしまう。

の2つのパターンに対応したいと思っています。

お礼日時:2009/09/30 08:21

あとついでですが, 実は


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$/ の判定をやめなさい。
という命令が出来ればいいのですが、いかがでしょうか。

補足日時:2009/09/30 08:12
    • good
    • 0

実はオペランドは「式」でかまわないので,


/^START$/ .. /^END$/ || eof
でよかったりして
    • good
    • 0
この回答へのお礼

こういう書き方が出来るのですね。
ご指摘の通りで、終了の文字ENDが見つからないまま
eofを迎えたときに問題の状況に陥いるのでした。
ご教示、ありがとうございました。

お礼日時:2009/09/30 08:12

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!