正規表現の先読みと後読みについて、
イマイチ認識がぼんやりしてたので少し詳しく整理してみた
コードはrubyで書いてます

  • 先後読みの書式

    • 先読み : (?=)
    • 後読み : (?<=)
    • 否定の先読み : (?!)
    • 否定の後読み : (?<!)

    特徴として、先後読みにマッチしたテキストはマッチから取り除かれる

  • 先読みの動き

    先読みはそれまでにマッチした位置を記憶して
    先読み部分にマッチするかをチェックする

    先読み部分にマッチしたら
    先読み部分を削除して記憶していた位置に戻る

    つまり、下記の例だと、
    最初の文字列aaabbbはマッチするが、aaacccはマッチしない

    str = "aaabbb aaaccc"
    str.scan(/aaa(?=bbb)/)
    => ["aaa"]
    

    さらに、マッチ位置が先読み部分の先頭位置になるため、
    先読みの後に以下のようにcccを足してみると何もマッチしない
    これは先読みのマッチ後、aaaの最後のaの直後に
    マッチしているので次の文字がbとなるため。

    str = "aaabbb aaaccc aaabbbccc"
    str.scan(/aaa(?=bbb)ccc/)
    => []
    # 何もマッチしない
    
    str.scan(/aaa(?=bbb)\w+/)
    => ["aaabbb", "aaabbbccc"]
    # マッチ対象を単語構成文字とするとaaabbbとaaabbbcccがマッチ
    
  • 後読みの動き

    後読みは正規表現エンジンが到達した位置のすぐ左に
    後読みに含まれているテキストが存在するかをチェックする
    後読み部分にマッチしたら、後読みマッチ部分を削除する
    下記の例だと、最初の文字列bbbaaaはマッチするが、cccaaaはマッチしない

    str = "bbbaaa cccaaa"
    str.scan(/(?<=bbb)aaa/)
    => ["aaa"]
    

    後読みの直前にcccを追加すると何もマッチしなくなる
    これは、後読みが現在位置の左側をチェックしに行くため、
    cccの部分を参照する事が原因(cccとbbbはどうやってもマッチしない)

    str = "bbbaaa cccaaa cccbbbaaa"
    str.scan(/ccc(?<=bbb)aaa/)
    => []
    # 何もマッチしない
    
    str.scan(/\w+(?<=bbb)aaa/)
    => ["bbbaaa", "cccbbbaaa"]
    # マッチ対象を単語構成文字とするとbbbaaaaとcccbbbaaaがマッチ
    
  • HTMLタグの中身だけを取り出す正規表現

    "normal bold normal"の様なHTMLテキストから
    bタグの中身だけにマッチする正規表現を考える

    str = "normal <b>bold</b> normal"
    regex = /(?<=<b>)\w+(?=<\/b>)/
    str.match(regex)
    => #<MatchData "bold">
    

    開始タグを後読みで、終了タグを先読みでマッチさせるのがポイント

    先読みは現在位置より右側をチェックして、
    後読みは現在位置の左側をチェックする
    そして、先後読みはグループのような記述だけど実際は
    アンカーのような、位置にマッチするだけのものである
    という理解が大事だと思う

    先後読みをマッチ結果から対象を除外する特殊なグループ、
    のように考えてしまうと

    regex = /(?=<b>)\w+(?=<\/b>)/
    

    こんな正規表現(前後両方を先読み)でも
    タグの中身が取れてしまうような勘違いをしてしまうが
    実際は開始タグの部分の先読みが完了した時点で<タグの直前に
    マッチしている事になるので\wではなにもマッチしない事になるので注意。