特定の文字列を含まない文字列にマッチする正規表現

2014/01/27 未だにこの記事が検索されるようなので、誤りパターンを目立たないようにしました。


特定の文字列を含まない文字列にマッチする正規表現を考えてみた。


例えば、「xyz」を含まない文字列にマッチする正規表現
否定先読みで処理すべき(否定先読みが使えない処理系はもうないでしょう)

^((?!xyz).)*$

行単位でない場合は、前後を適宜修正する必要がある。


否定先読みを使えない場合を考慮すると、

^(.*xyz.*|(.*))$

で、「$2」を使う。
この方向がシンプルでしょう。
ただ、マッチ数が増えるし、全てマッチしてしまうので、判定が素直じゃない。
それを嫌うなら、たまたま目にした教えてgooの方法ですね。



解説他

たまたま目にした教えてgoo
http://oshiete.goo.ne.jp/qa/4522181.html
なんか難しいのが書いてあったので、考えてみた。
オレ、アホだから、難しいのワカラナイ。(「オレ、猫」風)
、、、
オレ、アホだから、俺の頭でわかるの考える〜〜〜。

  • 「xyz」を含まない文字列
  • 「x」を含まないか、ごにょごにょ ([^x]*|...)
  • ごにょごにょは、xにyが続かないか、、、 ([^x]*|[^x]*x[^y].*|...) .*にxyzが入るけど後で考える
  • さらに、、、は、xyにzが続かなければOK ([^x]*|[^x]*x[^y].*|[^x]*xy[^z].*)
  • [^x]*多すぎ、.*部分には同じもの適用 [^x]*([^x]|x[^y]|xy[^z])*
  • ってか、先頭のは()内先頭に一致 ([^x]|x[^y]|xy[^z])*
  • ようは、x含まなければOKだし、xあってもyが続かなければOKだし、xyでもzが続かなければOKなだけ

これで良いと思うんだけど。
対象文字列が長くなればなるほど、正規表現も長くなるのでうんざりだけど、()も少ないので、まあ許してけろ。

でつなげて行くだけー。


、、、って思い込んでた。
コメントで指摘されて発覚、ダメじゃんこれ。


ということで否定先読みを使うべき、というところ。
先読み・後読みについては、以下を参照のこと。
http://www.mkamo.org/blog/20080626/62.html


あと、VBScript.RegexpのMultilineは、「^$」が改行にマッチするけど、同じ行でなくてもマッチすることに注意が必要。

否定先読みはちょっと、という場合

日本語だと当時上手く検索できなくて、英文でぐぐった際に、素晴らしいのを見つけた。
(ちょっと修正してあるけど)
RegEx to exclude a specific string constant - Stack Overflow

(.*xyz.*|(.*))

で、$2を使う。

検証

$tar = @'
	テストパターン書いてね
'@

$regex = New-Object -ComObject VBScript.RegExp
$regex.Multiline = $true
$regex.Global = $true
$regex.Pattern = "^(.*xyz.*|(.*))$"

$matches = $regex.Execute($tar)
for($i = 0; $i -lt $matches.Count; $i++) {
	if(($matches.Item($i).SubMatches.Count -gt 1) -and ($matches.Item($i).SubMatches.Item(1).Length -gt 0)) {
		Write-Host $i $matches.Item($i).SubMatches.Item(1)
	}
}

この発想が、素晴らしい。
xyzが含まれると、先の方にマッチするから、後ろを取り出せば良いとな!
マッチ数は増えるけど、気にスンナ!