正規表現

正規表現で否定(NOT,以外,含まない)を表すには文字クラスか先読み

正規表現で否定を表す方法

正規表現で否定を表したい場合には、大きく2通りの表し方があります。1つ目は対象文字を含まない場合で、これは文字クラスの否定にて表せます。2つ目は対象文字列(2文字以上)を含まない場合で、これは否定先読みを活用して表します。

正規表現での否定まとめ(置き換えればOK)

正規表現での否定表現をまとめると次の表のとおりになります。正規表現で否定を表すには大きく2とおりあるため、次のどちらに当てはまるかチェックしましょう。

  • 対象文字を含まない否定
    • 例:1も2も3も含まない
  • 対象文字列を含まない否定
    • 例:123のような連続パターンを含まない

対象文字を含まない否定

かきたい表現正規表現マッチする例マッチしない例
対象文字を含まない一文字[^123]4, 5, 61, 2, 3
対象文字を含まない一文^[^123]+$4, 45, 66661, 145, 673

対象文字列を含まない否定

かきたい表現正規表現マッチする例マッチしない例
対象文字列で始まらない一文^(?!123).+$0123, 12031230
対象文字列で終わらない一文^.+(?<!123)$1230, 12030123
対象文字列を含まない一文^(?!.*123).*$12030123, 1230
XXを含みYYを含まない文字列^(?=.*12)(?!.*23).*+$12030123, 1230

以下では上記の表現の成り立ちや構成について詳しく解説します。

対象文字を含まない否定

正規表現にて特定の文字を含まない否定を表したい場合には、角括弧ハットを使って「[^ ]」のように記載します。たとえば、ABC以外の文字を検索したい場合には「[^ABC]」のように表記します。

例1:対象文字を含まない一文字

例として「アイウエオ」のどれにも当てはまらない文字を正規表現で探したいと想定します。この場合、まずは「[アイウエオ]」のように角括弧[ ]の中に対象の文字を入れます。その後、それらの文字”以外”を意味する「^」を「[」の直後に設定して「[^アイウエオ]」とすればOKです。

// 正規表現:「アイウエオ」のいずれでもない文字
[^アイウエオ]

// 検索対象
ア
イ
カ
キ

// 検索結果
カ
キ

例2:対象文字を含まない一文

次の例では一文のなかのどこにも「アイウエオ」を含まないパターンを探したいとします。まずはさきほど作成した「[^アイウエオ]」に対して「+」をつけます。「+」は直前の文字を1文字以上繰り返す記号で、この場合は「[^アイウエオ]」に対してかかります。よって「アイウエオ」以外のみで構成された文字列を意味します。

これに行頭を意味する「^」行末を意味する「$」をつけることで、行頭から行末までが「アイウエオ」以外で構成された1文字以上の文を探し出せます。

// 正規表現:「アイウエオ」のいずれも含まない文字のみで構成された一文
^[^アイウエオ]+$

// 検索対象
ピカチュウ
カイリュー
ヤドラン
ピジョン

// 検索結果
ヤドラン
ピジョン
シャワーズ

「^[^アイウエオ]+$」のように、行頭を表す「^」と行末を表す「$」をつけないと文の途中からマッチするような意図しない挙動に引っかかります。上の例だと「ピカチュ」のように中途半端に検索にヒットしてしまいます。

これを回避するためにも、正規表現を書く際には意図的に「^」と「$」をつけるよう習慣づけるとよいでしょう。

[予習]否定先読み・否定後読みとは

「対象文字列を含まない否定」の仕組みを考えるにあたっては前提知識に、否定先読みと否定後読みが必要となります。この後さらっと解説をしますが、詳しく確認したい方は先に次の記事を確認するとよいでしょう。

否定先読みとは

否定先読みとは特定のパターンにマッチした場合に、その先(前)にアンカーを設置する書き方で「?!」のように記載します。言葉ではわかりづらいので、先に例をお見せします。

// 正規表現:「フシギバナ」のあとに「レベル20」以外が続く場合にマッチ
フシギバナ(?!レベル20).+

// 検索対象
フシギバナレベル20
フシギダネレベル40
フシギバナレベル40
フシギバナレベル60

// 検索結果
フシギバナレベル40
フシギバナレベル60

上記では「フシギバナ」のあとに「レベル20」以外の文字列が続く場合にマッチする正規表現を書いています。この表現は大きく「フシギバナ」「(?!レベル20)」「.+」の3つにわかれます。

  1. 「フシギバナ」に当てはまるか判定
  2. 「(?!レベル20)」に当てはまるか判定
    • 「(?!レベル20)」は「レベル20」以外の文字列の場合に、その直前位置にマッチ
      • 「レベル20」はNGなのでマッチしなくなる
      • 「レベル40」や「レベル60」はOKなので「レ」の直前位置にマッチする
  3. 「.+」に当てはまるか判定
    • 任意の一文字以上なため「レベル40」や「レベル60」はOK
  4. よって、1,2,3より「フシギバナ」を含み「レベル20」を含まずそれ以外の任意の一文字以上の文字列が続く場合にマッチする

否定先読みを使うとXXXXという文字列を含まないときにマッチさせるという芸当が可能です。そのため、後述の「対象文字列を含まない否定」でもこれを活用していきます。

否定後読みとは

肯定後読みとは、特定のパターンにマッチした場合にその先(前)にアンカーを設置する書き方で「?<!」のように記載します。肯定後読みについても例を示しながら紹介します。

// 正規表現:「フシギダネ」が来ない場合に限定してマッチ
.+(?<!フシギダネ)レベル\d{2}

// 検索対象
フシギバナレベル20
フシギダネレベル30
フシギバナレベル40
フシギバナレベル60

// 検索結果
フシギバナレベル20
フシギバナレベル40
フシギバナレベル60

上記では「フシギダネ」が来ないパターンに限定して「レベルXX」へマッチする正規表現を書いています。この正規表現は「.+」「(?<!フシギダネ)」「レベル\d{2}」の3要素にわかれます。

  1. 「.+」に当てはまるか判定
    • 任意の一文字以上であれば何でもOK(ここまではフシギダネも可)
  2. 「(?<!フシギダネ)」に当てはまるか判定
    • 「(?<!フシギダネ)」は「フシギダネ」以外のパターンの場合に、その直後へマッチ
      • フシギダネはNGなので、マッチしなくなる
      • フシギバナはOKなので、直後の位置にマッチする(厳密にはレベルXXがあることによってこの位置にマッチすることが決まる)。
  3. 「レベル\d{2}」に当てはまるか判定
    • 「レベル」+2桁の数値であればOK
  4. よって、1,2,3より「フシギダネ」で始まるパターン以外における任意の一文字以上と、「レベルXX」の文字列にマッチする

否定後読みは、否定先読みの逆で読んだ後の位置へマッチします。慣れるまではどちらがどちらかごっちゃになりますが、都度調べ直すと徐々にわかってくるかと思います。覚え方もあるにはあるので、載せておきますね。

ことば書き方覚え方
肯定先読み(?=XXXX)( )を読んで◯なら、( )の先(前)にマッチする
否定先読み(?!XXXX)( )を読んで×なら、( )の先(前)にマッチする
肯定後読み(?<=XXXX)( )を読んで◯なら、( )の後(うしろ)にマッチする
否定後読み(?<!XXXX)( )を読んで×なら、( )の後(うしろ)にマッチする

対象文字列を含まない否定

例3:対象文字列で始まらない一文

対象文字列で始まらない一文は「^(?!XXXX).+$」のように否定先読みを使ってかけます。「^(?!XXXX)」で先頭にXXXXを含まない意味を表して、「.+$」で任意の一文字以上が文末まで続くのを表現しています。

// 正規表現:
^(?!わざマシン).+$

// 検索対象
わざマシン001
わざマシン002
ひでんマシン003

// 検索結果
ひでんマシン003

例4:対象文字列で終わらない一文

対象文字列で終わらない一文は「^.+(?<!XXXX)$」のように否定後読みを使ってかけます。「^.+」では任意の一文字以上を、「(?<!XXXX)$」では末尾にXXXXが来ないことを表しています。

// 正規表現:
^.+(?<!シティ)$

// 検索対象
マサラタウン
ニビシティ
ハナダシティ

// 検索結果
マサラタウン

例5:対象文字列を含まない一文

対象文字列を含まない一文は「^(?!.*XXXX).*$」のようにかけます。「^(?!.*XXXX)」にて先頭からどこかの間にXXXXがあることを表して、「.*$」にて末尾までに文字列があってもなくてもOKなことを表現しています。

「例3:対象文字列で始まらない一文」と比較して、「(?!XXXX)」が「(?!.*XXXX)」のように「.*」を追記した形となっています。これにより、XXXXの前に何かしら文字列が入っても許容する構造となりました。

// 正規表現:
^(?!.*キング).*$

// 検索対象
ニドキング
キングラー
ヤドキング(ガラルのすがた)
ニドクイン
ヤドラン

// 検索結果
ニドクイン
ヤドラン

[応用]対象文字列を含む/含まないのかけ合わせ

これまでは一つの文字列を含まない場合を記載してきましたが、応用すればXXXXを含んだうえでYYYYも含んだ文字列を探すような技も可能です。

「XXXX」を含み「YYYY」も含む文字列

「XXXX」を含み「YYYY」も含む文字列を探したい場合には、「^(?=.*XXXX)(?=.*YYYY).*+$」のように2つの肯定先読みを入れることで調べられます。

// 正規表現:
^(?=.*フシギ)(?=.*ガメ).+$

// 検索対象
フシギダネ・ゼニガメ
フシギソウ・カメール
フシギバナ・カメックス

// 検索結果
フシギダネ・ゼニガメ

「XXXX」を含み「YYYY」も含まない文字列

「XXXX」を含み「YYYY」は含まない文字列を探したい場合には、「^(?=.*XXXX)(?!.*YYYY).*+$」のように肯定先読みと否定先読みを組み合わせることでで調べられます。

// 正規表現:
^(?=.*フシギ)(?!.*ソウ).+$

// 検索対象
フシギダネ
フシギソウ
フシギバナ
ゼニガメ
カメール
カメックス

// 検索結果
フシギダネ
フシギバナ
シャワーズ

「XXXX」を含まず「YYYY」も含まない文字列は「^(?!.*XXXX)(?!.*YYYY).*+$」のようにかけばOK!もちろん、3個以上のかけあわせも可能です!