• ベストアンサー

同条件で連続grepすると2回目がHITしない

grepを使った下記のようなプログラムの出力結果が理解できません。 --------------------------- @arr=("a"); $str="a"; @buf=grep{m/$str/g} @arr; print "buf=[@buf]\n"; @buf=grep{m/$str/g} @arr; print "buf=[@buf]\n"; --------------------------- [出力結果] buf=[a] buf=[] --------------------------- なぜ連続で同じ条件でgrepすると2回目はHITしないのでしょうか? なお、さらに繰り返しても、 buf=[a] buf=[] buf=[a] buf=[]  :  : のように一回おきにHITします。 また条件を一回ごとに変えるともちろんうまくHITします。 中で何が起きているのか理解できないので、 わかる方いらっしゃいましたら教えていただけないでしょうか? perlはActivePerl-5.8.7です。 なお、実際当方のプログラム内ではループの中で$strが変わっていきますが、 たまたま同じ検索文字列が続いたときにHITしなかったため、 上の簡易プログラムで試してみたところです。

  • eprj
  • お礼率100% (3/3)
  • Perl
  • 回答数3
  • ありがとう数3

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

  • ベストアンサー
  • t-okura
  • ベストアンサー率75% (253/335)
回答No.2

/g をつけるとマッチした位置から続けて検索が行われるため、 $arr[0] = "a" のときは、最初の grep では先頭の "a" に一致し、 二度目の grep では、文字列の末尾から検索するので一致しません。 最後まで検索したあと(検索結果が FALSE になったあと)は、再び文字列の 先頭から検索が開始されるため、三度目の grep では一致します。 また 最後まで検索しなくても /g で検索した時に一致した場所を 表す pos の値を変えることで検索開始位置を初期化することができます。 二つの grep の間に foreach my $arr (@arr) { pos($arr) = 0; } を入れて、検索開始位置を初期化すると、希望通りの結果となります。 ただ、例であげられたコードでは /g をつける必要はないように思いま す。

eprj
質問者

お礼

すばらしいです。 そういうことだったのですね。(目から鱗。。。 3度目にまた結果がうまく出ることも理解できました。 実際には@arrはCSVファイルを読み込んだものが格納されていて、 マッチングする行が複数あるため/gが必要だったのです。 posで検索位置を初期化することで希望通りの結果が得られました。 本当にありがとうございました。

その他の回答 (2)

  • kabaokaba
  • ベストアンサー率51% (724/1416)
回答No.3

#前半部分はNo.2さんと本質的に同じだったから削除(^^;;; #せっかくかいたので,後半だけアップします. $a="xxxxx"; for (1..10){ $a=~m/x/g; print pos($a); } これの結果をみてもらえるとわかると思いますが, $aを/gでマッチさせたときには 「マッチ位置」pos($a)が付随します. そして,マッチ失敗したら,クリア(undef)されます. #参照:perldoc -f pos posとかは,/gcや\Gなんかと合わせて使われますようですね

eprj
質問者

お礼

こちらもありがとうございました。 実際上記のサンプルを試してみましたが、 123451234 という結果でした。 posが1、2、...と変化していき、 6回目のループではFalseになるので出力がなくなるんですね。 とても理解しやすいです。 おかげで解決できました。 本当にありがとうございました。 これでこの質問はクローズさせていただきます。

  • Ceren
  • ベストアンサー率49% (90/183)
回答No.1

手元でちょっと試してみました。 どうした加減でそうなるかはよく解りませんが、 正規表現につけている「g」オプションが原因のようです。

eprj
質問者

お礼

No.2のお礼欄にも書きましたが、/gオプションは必要だったんです。 でも/gオプションをつけるとポジションを意識しないといけないということにつながりました。 ご検討いただきありがとうございました。

関連するQ&A

  • 一番大きい奇数を表示する

    scanf関数を使用して、文字列を10回入力し一番大きい文字列を表示するプログラムを作ったのですが、 一番大きい「奇数」を表示するように条件を加えた場合どうすればよいのでしょうか? 偶数=割り切れる 奇数=割り切れない ということまでは分かるのですがその先が分かりません。 一応一番大きい文字列を表示するプログラムを貼っておきます。 #include <stdio.h> int main(void) { char str[1024]; char buf[10]; int i; printf("文字列を10回入力して下さい:\n"); memset(str, 0, sizeof(str)); for (i = 0; i < 10; i++) { memset(buf, 0, sizeof(buf)); printf("input>\n"); scanf("%s", buf); } if (strcmp(buf, str) > 0) { strcpy(str, buf); } printf("output>\n%s\n" , str); getchar(); }

  • CGIでのgrep関数においての質問

    Perl言語で作成するCGIプログラムについての質問なのですが、grep関数をつかって文字のマッチングを試みて、1回目はうまくいったのですが、同じように2回目を同じプログラムで使ったところ、絶対に0件マッチになります。検索対象も、検索文字列も、スカラー変数でだしてみて目で確認して明らかに合致しているのに0件になります。 ちなみに、使用した関数は以下のような感じです。 @hit = grep(/$data1/,@data2); $hit = grep(/$data1/,@data2); $hitでだしてみても0になりますし、@hitも何も入っていません。 grep関数は2回使えないのでしょうか。 検索文字はメールアドレスなのですが、前のプログラムで同じように関数を使って成功していますので、検索文字が原因ではないと思いますが。 ご回答お願い致します。

    • ベストアンサー
    • Perl
  • ループ内での後方参照の使用に関して

    下記のようなプログラムを作成しました。 (Perl のバージョンは 5.8.8 となります。) for ($i=0; $i<=105; $i++) { $str1 = "str1"; $str2 = "1234567890str1test"; $str2=~ s/^(\d+)($str1)/test/g; print $i."\n"; print $1."\n"; print $2."\n"; print $str2."\n"; } このプログラムを実行しますと、ループが100回実施するまでは $1, $2の値を 取得できるのですが、100回を超えると取得できなくなります。 (上記で行っている置換処理は100回を超えても正常に処理されます。) 質問としましては、この現象は Perl のバグなのでしょうか。 それとも、私の正規表現の書き方に不備があるのでしょうか。 ネットでいろいろ調べてみたのですが、答えが見つからなかったため、 質問させていただきました。 よろしくお願い致します。

  • perl の grepで 全角カタカナで検索できません

    perl の以下のソースで、実行エラーになってしまいます。 grep で全角カタカナで検索できないのです。半角カタカナでは、うまく動きました。 全角文字(たとえば’中華’)でも正しく「中華料理」と出力されます。 ’ラーメン’で検索したら、’ラーメン店’と出力されるのが正しいと思うのですが、 なぜ動かないんでしょ? ↓ソース #!/usr/bin/perl @aaa = ("ラーメン店","カレーハウス","中華料理"); @aaa = grep /ラーメン/ , @aaa; print "Content-type: text/html\n\n"; print "<META http-equiv=\"Content-Type\" content=\"text/html; charset=Shift_JIS\">\n"; print <<"HTML"; @aaa HTML exit;

  • 一番大きい奇数を表示する

    scanf関数を使って数字を10回入力して一番大きなものを表示させるプログラムをつくったのですが、 さらに一番大きな奇数を表示するにはどうすればいいのでしょうか? 偶数=割り切れる 奇数=割り切れない というところまでは分かるのですが、以下のプログラムに奇数を判別するソースを追加するのにはどうすればいいのでしょうか。 #include <stdio.h> int main(void) { char str[1024]; char buf[10]; int i; int w; printf("文字列を10回入力して下さい:\n"); memset(str, 0, sizeof(str)); for (i = 0; i < 10; i++) { memset(buf, 0, sizeof(buf)); printf("input>\n"); scanf("%s", buf); } for (i = 0; i < 10; i++) { if ((buf[i] & 1) == 1) /* 奇数であるか */ { if (strcmp(buf, str) > 0) { strcpy(str, buf); } } } printf("output>\n%s\n" , str); getchar(); }

  • *の使い方ですが

    @Arr=("This","is","sample"); &Arr_print(*Arr); sub Arr_print { *a=@_[0]; foreach $x(@a) { print"$x\n"; } } を @Arr=("This","is","sample"); &Arr_print(*Arr); sub Arr_print { @a=@_[0];// *a → @a に変更 foreach $x(@a) { print"$x\n"; } } と書いてもよさそうなものですがそうはいきません どうしてでしょうか?

    • ベストアンサー
    • Perl
  • tail -f の出力に|(パイプ)を2回

    tail -f の出力への|(パイプ)を2回行ったものの結果が出力されず困っています。 具体的には、下記のようなコマンドを実行しています。 tail -f hoge.txt | sed -e 's/aaaa//g' | grep 'moge' 2個目のsedのみにすれば問題なく出力されるのですが、最後のgrepを行うと何も出力されなくなってしまいます。(sedの結果がgrepに渡れば必ず出力されるはずのもの) |(パイプ)の仕組みの理解が曖昧な気がしているのですが、なぜこうなってしまうのかがわかる方がいらっしゃれば教えてください。

  • ■str_split()で指定した文字数ではなく、指定した文字までを配列に入れることは可能ですか?

    いつもお世話になっています。 文字列を配列に入れようと思って、str_split()を調べました。 $str = "Hello Friend"; $arr = str_split($str, 3); print_r($arr); の結果は Array ( [0] => Hel [1] => lo [2] => Fri [3] => end ) になりますよね?これを $str = "今日はいい天気です。遠足に行きましょう。楽しかったですね。"; としたときに、「。」までを区切りとして配列に入れることは可能でしょうか? つまり、この後何かしらのことをして、 print_r($arr); をすると、以下のように出力されるようにしたいのです。 今日はいい天気です。 遠足に行きましょう。 楽しかったですね。 str_split()で指定した文字数ではなく、指定した文字までを配列に入れることは可能でしょうか?

    • ベストアンサー
    • PHP
  • プログラム

    { FILE *fp; char *str,buf[1000];   char subbuf[100],*s1; char buf2[100],*s2;   char buf3[100],*s3; char buf4[100],*s4; static double bx=0; static double by=0; if ((fp = fopen("test.txt", "r")) == NULL) { printf("ファイルが開けません\n"); return EXIT_SUCCESS; } while (fgets(buf, 1000, fp) != NULL) { str=buf;        while((*str!='\0'){ if(*str!='\0' && *str=='G'){ *str++; if((*str!='\0' &&(isdigit(*str) || *str=='-' || *str=='.')) s2=buf2; if((*str!='\0' &&(isdigit(*str) || *str=='-' || *str=='.')) while(*str!='\0'&&(isdigit(*str) || *str=='-' || *str=='.')) *s2++=*str++; *s2='\0'; } if(*str!='\0' && *str=='X'){ *str++; if((*str!='\0' &&(isdigit(*str) || *str=='-' || *str=='.')) s3=buf3; if((*str!='\0' &&(isdigit(*str) || *str=='-' || *str=='.')) while(*str!='\0' &&(isdigit(*str) || *str=='-' || *str=='.')) *s3++=*str++; *s3='\0'; } if(*str!='\0' && *str=='Y'){ *str++; if((*str!='\0' &&(isdigit(*str) || *str=='-' || *str=='.')) s4=buf4; if((*str!='\0' &&(isdigit(*str) || *str=='-' || *str=='.')) while(*str!='\0' &&(isdigit(*str) |*str=='||*str=='.')) *s4++=*str++; *s4='\0'; } CDC *v; v=GetDC(); if((int)atof(buf2)==92){ MoveTo((int)atof(buf3),(int)atof(buf4)); bx=(int)atof(buf3); by=(int)atof(buf4); ReleaseDC(v); } else if((int)atof(buf2)==01){ MoveTo((int)bx,(int)by); LineTo((int)atof(buf3),(int)atof(buf4)); bx=(int)atof(buf3); by=(int)atof(buf4); ReleaseDC(v); } else str++; } } fclose(fp); return 0; } という風なプログラムになっているのですが 今現在 G01X30Y30 G01X30Y120というファイルをこのプログラムにて実行すると (0,0)→(30,30)→(30,120)というような直線が引かれます。 しかしこれを G01X30Y30 Y120 とかかれたときも同様の結果がでるようにしたいです。 つまり最初のG○○が省略されているときは前回のGの値を X○○が省略されているときは前回のXの値を使うように 変更したいのですがどのように変更すればいいのかがわかりません。 教えてください。

  • grep のファイルの検索で

    grepを使う練習をしています。 ネット上の問題で 「 nとnの間に nより前の文字(a~mのこと)しか入らないような名前のファイルを見つけよ。例えば、 Abelonian,Aberdonian,Abernant.といったところ。consnantなどはnとnの間にnより後の文字(s)がくるので表示しない。ちなみにnとnの間は何も入らなくてもよい」 という問題です。 grep n[a-n^]*n filename と入力したのですが、できません。 どなたかアドバイスいただけないでしょうか。よろしくお願いします。

専門家に質問してみよう