• ベストアンサー

範囲演算子と文字列マッチングを組み合わせたときの解除方法

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

  • goku3
  • お礼率78% (97/123)
  • Perl
  • 回答数9
  • ありがとう数9

質問者が選んだベストアンサー

  • ベストアンサー
  • kumoz
  • ベストアンサー率64% (120/185)
回答No.8

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

goku3
質問者

お礼

すみません、条件データが良くなかったので、次のように訂正します。 -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 これが今回、期待した結果で、バッチリでした。ありがとうございました。 お礼が遅くなり、大変申し訳ありませんでした。

goku3
質問者

補足

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

その他の回答 (8)

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.9

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

goku3
質問者

お礼

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

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.7

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

goku3
質問者

お礼

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

  • kumoz
  • ベストアンサー率64% (120/185)
回答No.6

>(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)
回答No.5

おっと, ファイルハンドル 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)
回答No.4

ざっと調べた感じでは, 残念ながら単純にはできなさそうです. ただし, さすがに Perl なので黒魔術を使えば何とかなるかもしれません. 例えば while (@ARGV) { $ARGV = shift @ARGV; my $cond = eval 'sub { /^START$/ .. /^END$/; }'; while (<ARGV>) { if (&$cond) { なんかする last if なんか } } で回避できているかも.

goku3
質問者

お礼

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

回答No.3

上手くいくようですが。もしかして質問の理解がまちがってます? #!/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

goku3
質問者

お礼

実際に動作をご確認頂いて恐縮です。ありがとうございます。 質問が分かりづらくて申し訳ありません。 #1,#2の方へのコメントとして書かせて頂きましたが、 (1)あるファイルには「END」が書かれていない場合がある。 (2)START~ENDの間に別のwhileループ脱出条件があって、END判定が成立しないまま次のファイルへ移ってしまう。 の2つのパターンに対応したいと思っています。

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.2

あとついでですが, 実は while (<>) { chomp; if(/^START$/ .. /^END$/ || eof){ なんかする } } でいいのかもしれない.

goku3
質問者

補足

何度もご教示ありがとうございます。 はい。それで、この if(/^START$/ .. /^END$/ || eof){ ・・・・ } ブロックの中で、ある別の条件が成立したために、 lastで、whileを抜けさせた場合・・・例えば、 while (<>) { chomp; if(/^START$/ .. /^END$/ || eof){   なんかする。   last if(/^2009/); } } のようにしていた場合に、次のファイルの処理に入ると問題の状況になるようです。 lastでwhileを抜けた後、 /^START$/ .. /^END$/ の判定をやめなさい。 という命令が出来ればいいのですが、いかがでしょうか。

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.1

実はオペランドは「式」でかまわないので, /^START$/ .. /^END$/ || eof でよかったりして

goku3
質問者

お礼

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

関連するQ&A

  • 特定文字列で囲まれた範囲を抜き書きするためには?

    特定の文字列で開始され、特定の文字列で終了するテキストの一部を抜き出すためには、コマンドラインからは perl -ne 'print if /開始文字列/ .. /終了文字列/' file.txt でできると思うのですが、これがたとえば $page 変数に入ったテキストで同様の処理を Perl 内部のスクリプトで行う時にはどうすればいいのでしょうか? while(<$page>){ ... } とかやって1行1行処理してみようと思ったのだけど、できません。

    • ベストアンサー
    • Perl
  • 指定した文字列を含む行から、指定した文字列を

    含む行まで、全ての行を抽出して別ファイル(名前は元ファイルと同じ)としたいのです。 対象ファイルに結果を上書きでもかまいません。 指定したフォルダの中にある複数のフォルダ各々に1つずつ入っているテキストファイルが対象になります。 例) 東京都 新宿区 <start123AAA> 東京都 港区 <end>AAABBB 東京都 目黒区 どのファイルにも必ず<start と<end の文字列があります。(必ず1組です) <start の文字列を含む行から <end の文字列を含む行までを 抽出したいのです。 (欲しい結果↓) <start123AAA> 東京都 港区 <end>AAABBB フリーのエディタソフトのgrep、grepのマクロ、バッチでできないものかと試してみているのですが どうしてもうまくいきません。 抽出したい範囲、削除する範囲には禁則文字?が入っているのでバッチは難しいのでしょうか? できればバッチで処理できればなと思っているのですが、うまくいきません。 (<>!""/ あたりが入っています。別の文字に置き換えることは可能です) 範囲を抽出ではなく、 <startを含む行の一つ上の行から前を全て削除 <endを含む行の一つしたの行から後を全て削除 する方法や VBAで、スタート行をとエンド行をSearchして抽出する方法など ご教授いただきたくお願い致します。

  • 文字列をカウントする方法

    ファイルを読み込んで、その中の特定の文字列たとえば"映画"が何個あるかを調べるプログラムを作っているのですが、動作しません。ファイルには、改行やタブが入っているのですが、問題はファイルの読み込み方なのか、カウンタの方なのか分かりません。次がプログラムですので何か分かる方は教えてください。 if (open(FH, "data/log.txt")) { @file = <FH>; close(FH); } while(<STDIN>){ $movie=0; if(@file == "映画" ){ $movie++; } print $movie; }

  • エクセルVBAについてお尋ねいたします。以前、列の検索範囲から指定列中

    エクセルVBAについてお尋ねいたします。以前、列の検索範囲から指定列中の空欄セルが存在した際にその空欄を含む行の削除を実行するマクロを組んだのですが、削除する行の範囲も指定するマクロに組み替えたく望んでおります。 <参照> Private Sub CommandButton1_Click() Dim minRow As Integer Dim maxRow As Integer Dim checkColIndex As Integer ' 初期値1(開始行の値) minRow = 1 ' 初期値2(終了行の値) maxRow = 10 ' 検索列の値(1=A列、2=B列、3=C列...) checkColIndex = 1 Application.ScreenUpdating = False ' 空欄が存在する行の削除処理 For i = maxRow To minRow Step by - 1 ' 指定セルが空欄の場合、その行を削除 If (Len(ActiveSheet.Cells(i, checkColIndex).Value) = 0) Then ActiveSheet.Rows(i).Delete End If Next Application.ScreenUpdating = True End Sub 上記のコマンドを例にすると、 1行目から10行目までのA列に存在する空欄を検索し、空欄が存在したら範囲中の最下(10行目)よりセルを含む行ごと削除・・・ となりますが、この中に <例> ’削除を行う範囲(1=A列、2=B列、3=C列...) ' 開始列値 *** ' 終了列値 *** '範囲内の行を削除 *** など、検索範囲が変わった際にも対応が可能で削除範囲が指定出来るコマンドに変更したく望みます。 宜しくお願い致します。

  • VBScriptで指定した範囲の中で対象の文字列が含まれている件数を数えたい

    VBScriptで指定した範囲の中で対象の文字列が含まれている件数を数えたいと思っています。 やり方が思いつかずわからないので助けてください。 ログの内容は下記のようになっています。 /*/*/*/*/*/*/*/*/*/*/* 2009/08/08 AM9:00 LOG START /*/*/*/*/*/*/*/*/*/*/* PC01 08:00 PC05 08:10 PC100 07:00 /*/*/*/*/*/*/*/*/*/*/* LOG END /*/*/*/*/*/*/*/*/*/*/* /*/*/*/*/*/*/*/*/*/*/* 2009/08/08 AM10:00 LOG START /*/*/*/*/*/*/*/*/*/*/* PC101 09:00 PC55 09:20 PC111 08:00 PC101 09:50 /*/*/*/*/*/*/*/*/*/*/* LOG END /*/*/*/*/*/*/*/*/*/*/* . . . 一時間ごとにログに情報が書き込まれているわけですが「LOG START」から「LOG END」までの間に指定文字列が含まれる行をカウントしたいと思っています。 その一時間後との値をエクセルに保存していきたいのですが・・・ ヒントなどいただけないでしょうか?

  • セル範囲指定方法

    VBAにて下記作成中ですが、行き詰ってしまいました。 どなたか、ご教授願います。 Sub 転記ボックス1_Click() Sheets("S").Select Range("N13").Select If ActiveCell.Value <> "" Then Selection.Copy Sheets("H").Select Range("K65536").End(xlUp).Offset(0, 1).Select --->シートH、K列最終行の右隣からL列最終行の範囲を指定 上記指定範囲内全てに、シートS・N13の値を貼付 ElseIf ActiveCell.Value = "" Then Sheets("H").Select Range("K65536").End(xlUp).Offset(0, 1).Select --->シートH、K列最終行の右隣からL列最終行の範囲を指定 上記指定範囲内全てに、”シートS・N13”と入力 End If End Sub --->部分の書き方がわかりません。 よろしくお願いします。

  • VBAで列範囲、行範囲の指定方法の質問です。

    例えば、3行目から5行目を選択する記述、Rows("3:5").Select の記述で行番号を変数で指定することができますか? 要求側で   r1=3   r2=5 と指定してサブルーティンに渡し、 Rows("3:5").Select と同じ効果を期待したいのです。 なお、指定範囲が1行の場合は、次の記述で実現しています。 Sub 列の選択(Sheet as Ojbect, RowNo)   Sheet.Rows(RowNo).Select End Sub アクティブ シートへの処理なので、別にWorkSheet オブジェクトの指定は不要だと思うのですが、次の記述ではエラーで成功しません。 Sub 列の選択(RowNo)   Rows(RowNo).Select End Sub 結論として次のような Subroutine の作成が希望です。 Sub 複数列の選択(Sheet as Object, Row1, Row2) ・・・ End Sub

  • 文字列検索のAND条件を引数にしたい

    Perl初心者です。 指定したファイルの中から指定した文字列の入っている行を取得したいのですが、 open(FL, $ARGV[0]); while (<FL>) { if ($_ =~ /$ARGV[1]/) { print $_; } } close(FL); としたとき、 $ARGV[1]に aaa/bbb/ccc と入れてaaaかつbbbかつcccを含む行としたいのですが、うまく動作しません。 このような場合がGREPとかのコマンドを使ったほうがよいのでしょうか? 別の処理も組み込みたいので、できればperlで書きたいのですが、正規表現の文字列を引数で与えるようなことはできるのでしょうか? よろしく御願いします。

    • ベストアンサー
    • Perl
  • 文字列の比較

    現在Cでプログラムをつくっているのですが いきずまってしまいました。 1.テキストファイルを読み込む 2.書き込みファイルを開く 3.読み込んだデータを一行読み込んで   その行の特定の文字列があれば、   特定の文字列のみ取り出し、   書き込みファイルに書く。    4.次以降の行も同じ処理をする。    5.読み込み、書き込みファイルを閉じる。 と、こんな感じのプログラムなのですが、 3の特定の文字列をどのように取り出せばいいのかわかりません。 取り出したいのが数字ならば、if文でできるのですが 文字列の場合は、どうなんでしょうか。 例えば、「MOJIRETU11」という取り出したいとき 数字と同じようにIF文を使用することは、できるのでしょうか。

  • エクセル 特定の文字列から範囲を指定

    エクセル 名前定義の範囲について質問です。 始まり・終わりの行・列を示す特定の文字列を設定し、 それを元に自動で範囲を変化させるようなことはできませんでしょうか。 例えば、 B1:列ここから D1:列ここまで A3:行ここから A6:行ここまで と書かれていた場合、 名前定義の範囲が自動で「B3:D6」となるような方法を探しています。 なお、 ・列を指定する文字列は必ず1行目に ・行を指定する文字列は必ずA列目に あるという想定です。 極力マクロを使用しない方法で、有効なものがございましたら ご指導のほどよろしくお願いします。

専門家に質問してみよう