【正規表現】先読み,否定の先読み,についての質問です.

/usr/home/bin/hoge.txt
という文字列に対して,最後のスラッシュより後の文字列(hoge.txt)を取り出したいと思います.
まず私が考えた正規表現では
(?!.+/).+
というものですが,これだと,「/hoge.txt」にマッチしてしまいます.
+を*に変え,
(?!.*/).+
とすると,正しく「hoge.txt」にマッチします.
私の解釈では
(?!.+/)
は,「スラッシュで終わる,1文字以上の文字列のマッチが失敗する位置」という意味で,
(?!.*/)
は,「スラッシュで終わる,0文字以上の文字列のマッチが失敗する位置」という意味で,
この場合,どちらも同じ位置となるはずなのですが,なぜか正規表現の挙動は異なります.
これはなぜでしょうか? また,私の解釈はどこが間違っているのでしょうか?

回答の条件
  • 1人5回まで
  • 13歳以上
  • 登録:2012/12/21 09:52:02
  • 終了:2012/12/21 13:27:55

ベストアンサー

id:ku__ra__ge No.3

ku__ra__ge回答回数118ベストアンサー獲得回数402012/12/21 12:02:43

(?!.+/)
は,「スラッシュで終わる,1文字以上の文字列のマッチが失敗する位置」という意味
(?!.*/)
は,「スラッシュで終わる,0文字以上の文字列のマッチが失敗する位置」という意味

認識はこれで合っています。
ただ「(?!.+/).+」という正規表現は、否定先読みが『後』の位置に書かれていることが混乱の元ではないかと思います。
(一般的な横書きテキストの場合、正規表現の「先」は右側であり、「後」は左側です)

「(?!.+/)」は、「その位置から右側へマッチを試みた場合、スラッシュで終わる,1文字以上の文字列のマッチが失敗する位置」を示すため、それぞれの位置でのマッチ成否は以下のようになります。

【位置A】
/usr/home/bin/★hoge.txt
              ↑
              (「.+/」のマッチは失敗する)

【位置B】
/usr/home/bin★/hoge.txt
             ↑
             (「.+/」のマッチは失敗する ※)

※正規表現「.+/」は2文字目以降に「/」が現れないとマッチしないが、
 「/hoge.txt」は1文字目にしか「/」が無いためマッチしない。

【位置C】
/usr/home/bi★n/hoge.txt
            ↑
            (「.+/」のマッチが成功する)

正規表現はマッチする文字列が最長になるようにマッチしますので「(?!.+/).+」は右端の「.+」が最長になるように【位置B】が利用され、結果「/hoge.txt」がマッチすることになります。

その他の回答(2件)

id:toymany No.1

ヤマヤタケシ回答回数4ベストアンサー獲得回数02012/12/21 10:23:52

この正規表現でいかがでしょうか?
/\/([^\/]+)$/


perlのテストコードです。(regrex_test.pl)

#!/usr/bin/env perl

$src="/usr/home/bin/hoge.txt";
$src =~ /\/([^\/]+)$/;
print( $1 . "\n" );


実行結果
./regrex_test.pl
hoge.txt

id:salon_hiyake

すみません,私が聞きたいのは「正しい解答」や「動くコード」ではなく,
否定の先読みにおける正規表現が,「どういう意味で,なぜ,そのような挙動を示すのか?」という意味と理由の部分です.

id:a-kuma3 No.2

a-kuma3回答回数4367ベストアンサー獲得回数18032012/12/21 11:55:15

参考になるのは、こちら。

"(?=hoge)" は "hoge" が続くゼロ文字列に一致する。

http://d.hatena.ne.jp/ku__ra__ge/20070324/p2

ダイアリーに記載されている京大のページはリンクが切れていますが、Internet Archive に残ってます。
http://web.archive.org/web/20030427151113/http://www.cog.ist.i.kyoto-u.ac.jp/~takahashi/notes/regex_forward_match.html

id:ku__ra__ge さんと同じく、ぼくもこれを読んで、「う~む」って唸りました。


んで、質問されていることの説明です。

(?!.+/)

この部分を、どう読むかというと、「『任意の一文字以上の文字列で、/ でおわるもの』ではない文字列の先頭(文字の隙間)」です。
hoge.txt の前にある "/" (スラッシュ一文字) は、「『任意の一文字以上の文字列で、/ でおわるもの』ではない文字列」に該当しますから、先の表現は "/" の前を指します。
そこから任意の文字列ですから、"/hoge.txt" がマッチするわけです。

対して、

(?!.*/)

さきと同様に、「『任意のゼロ文字以上の文字列で、/ でおわるもの』ではない文字列の先頭(文字の隙間)」です。
「『任意のゼロ文字以上の文字列で、/ でおわるもの』ではない文字列」に該当するのは、"hoge.txt" になります。
後は、説明は要らないですよね。

これを踏まえると、以下の正規表現も "hoge.txt" にマッチするのが理解できると思います。

(?<=.+/).+

「幅ゼロの肯定後読み」を使ってます。
「"/" で終わる文字列の末尾(文字の隙間)」までが、肯定後読み (?<=χ) の部分ですから...


なんて、偉そうに書いてますが、先読み/後読みって、難しいです。
先読み/後読みの正規表現は、ひとしきり考え込まないと理解できません X-)


蛇足ですが、元の問題の一番わかりやすい正規表現は、これだと思います。

[^/]+$
id:ku__ra__ge No.3

ku__ra__ge回答回数118ベストアンサー獲得回数402012/12/21 12:02:43ここでベストアンサー

(?!.+/)
は,「スラッシュで終わる,1文字以上の文字列のマッチが失敗する位置」という意味
(?!.*/)
は,「スラッシュで終わる,0文字以上の文字列のマッチが失敗する位置」という意味

認識はこれで合っています。
ただ「(?!.+/).+」という正規表現は、否定先読みが『後』の位置に書かれていることが混乱の元ではないかと思います。
(一般的な横書きテキストの場合、正規表現の「先」は右側であり、「後」は左側です)

「(?!.+/)」は、「その位置から右側へマッチを試みた場合、スラッシュで終わる,1文字以上の文字列のマッチが失敗する位置」を示すため、それぞれの位置でのマッチ成否は以下のようになります。

【位置A】
/usr/home/bin/★hoge.txt
              ↑
              (「.+/」のマッチは失敗する)

【位置B】
/usr/home/bin★/hoge.txt
             ↑
             (「.+/」のマッチは失敗する ※)

※正規表現「.+/」は2文字目以降に「/」が現れないとマッチしないが、
 「/hoge.txt」は1文字目にしか「/」が無いためマッチしない。

【位置C】
/usr/home/bi★n/hoge.txt
            ↑
            (「.+/」のマッチが成功する)

正規表現はマッチする文字列が最長になるようにマッチしますので「(?!.+/).+」は右端の「.+」が最長になるように【位置B】が利用され、結果「/hoge.txt」がマッチすることになります。

id:salon_hiyake

ありがとうございました.やっと腑に落ちる感じがしました.
これっぽっちのことで,丸一日考え込んでしまったので.やはり質問してみるものですね.

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

絞り込み :
はてなココの「ともだち」を表示します。
回答リクエストを送信したユーザーはいません