開発ブログ

WWWクリエイターズが送る、Git、CSS、HTML、コマンドライン、Macの便利機能など、開発に関する役立ち情報発信します。気まぐれに更新。

正規表現:文字やパターンを「含まない」否定の表現まとめ

最終更新:2017-12-08 by Joe

特定の文字や、文字列(パターン)を含まない否定の意味を持った正規表現についてです。特に「否定先読み・戻り読み」を利用した表現は、その仕様が混乱しやすいため、整理しながら補足にまとめました。

否定の正規表現について

「〜を含まない」といった、否定の正規表現を記述する場合、目的に応じて下記のような解決方法が思いつきます。

特定の1文字、正規表現パターンを否定するかで、おおきく方針が異なります。

否定の正規表現の実現方法

特定の1文字だけを否定したい場合は非常にシンプルで、「文字クラス」、もしくはエスケープシーケンスで表される文字型を利用する事で表現できます。

一方で、特定の正規表現パターンを否定したい場合、少し記述が複雑になります。今回は「否定先読み」を利用した表現をご紹介します。

それでは、順番に詳しく見ていきます。

[1] 特定の一文字を含まない文字列の正規表現

(a)「否定の文字クラス」を利用した表現

まず前提知識として、正規表現には「指定した文字のうち、いずれかの文字」といったパターンを表現できる、「文字クラス」という記法がありました。

さて、このような文字クラスは、[^文字 ] のように、「^(キャレット)」を開始カッコの直後に付加することで「否定の文字クラス(Negative Character Class)」の表現を行うことができます。キャレットがある事で、「カッコ内に指定した文字以外の文字」といった意味をもったパターンに変わるのです。

// 文字クラス: AかBかCのいずれかの一文字
[ABC]

// 否定の文字クラス: AかBかCのいずれか以外の一文字
[^ABC]

下記にいくつか使用例をあげます。

例) アルファベットを含まない文字列

文字クラスでは、「-(ハイフン)」を利用して、「[a-z]」と記述すれば、文字コード上の範囲「a から z まで」を指定することができました。これをそのまま否定文字クラスの表現にする事ができます。同時に量指定子の「+(1文字以上の連続)」を組み合わせると「アルファベット以外で構成された文字の連続」をマッチすることができます。

// アルファベット(小文字、大文字)以外の文字の連続
[^a-zA-Z]+

例) スペースを含まない文字列

正規表現において、空白(スペース)は、エスケープせず、そのまま記載できるのでした。また、下記の例で使用する両指定子「+」は、「直前のパターンの1回以上の連続」を表します。

// 半角スペースを含まない1文字以上の連続
[^ ]+

// 全角スペースを含まない1文字以上の連続
[^ ]+

// 全角か半角化のスペースを含まない1文字以上の連続
[^ | ]+

(b)エスケープシーケンスによる否定表現

バックスラッシュを使った「エスケープシーケンス」の中には「XXX以外の一文字」を表すものがあります。これらは上記の文字クラスで行うような表現(それ以外の文字も)簡素に記述することができます。

例)空白文字を含まない文字列

さて、文字クラスではありませんが、「\s」は特殊文字の1つで、「空白文字(半角スペース、全角スペース、タブ、改行、改ページ)」を表すことができます。

これの反対となるメタ文字「\S」で「空白文字以外の一文字」を表現できます。下記は、空白スペースだけでなく、より上記のような複数の種類の空白文字を含まない表現が記述できます。

// 空白文字を含まない文字の連続
\S+

さて、「空白文字以外」の例でしたが、他にもいくつかの「〜以外の一文字」を表すエスケープシーケンスが存在します。目的に応じてうまく利用することで、正規表現をシンプルに記述できるでしょう。

「〜以外の一文字」を表現するエスケープシーケンスの例

表現意味
\s空白文字(半角スペース、\t、\n、\r、\f)すべての文字。( |\t|\n|\r|\f)と同義
\S空白文字以外のすべての文字
\d数字。[0-9]と同義
\D数字以外の文字列。[^0-9]と同義
\wすべてのアルファベットとアンダースコアのうち任意の一文字。[a-zA-Z0-9_]と同義
\Wすべてのアルファベットとアンダースコア以外の1文字[^a-zA-Z0-9_]と同義
\lすべての半角英小文字のうち1文字
\Lすべての半角英小文字の以外の文字1文字(英大文字、数字、全角文字など含む)
\uすべての半角英大文字のうち1文字
\Uすべての半角英大文字以外の1文字(英小文字、数字、全角文字など含む)

これ以外のエスケープシーケンスに関しては、こちらの記事もご覧ください:

[2] 特定のパターンを含まない文字列の正規表現

上記の文字クラスで表現できたのは「指定文字以外の文字」といった記述で、あくまで一文字単位でしか否定を表現することができませんでした。一方で、実際に正規表現を用いて記述したパターンそのものを否定したマッチング表現(=パターンの否定表現)も存在します。「パターンの否定」を利用する正規表現に「否定先読み」「否定戻り読み」があります

今回、「否定先読み」を利用した記述方法をご紹介します。否定先読みとは、「(?!」「)」でくくったサブパターンがマッチしないことを条件にして、アンカーをマッチングする記述法です。

否定先読みの書き方
否定先読み(Negative Lookahead)の記述法

否定先読み・戻り読みの補足解説をこの記事の末尾に追加しましたので、ご一読下さい。

(a) 特定のパターンで開始しない文字列

否定先読みを用いて、「^」が示す「文頭」の直後に含まない文字列をマッチングします。

// PATTERNで開始しない文字列の表現 
^(?!PATTERN).*$

この「.*」で改行以外の文字列の0回以上の連続を表していますが、こちらは任意に変更できます。また、今回処理対象のテキスト範囲を明示するため、すべて「^(行頭)」「$(行尾)」で囲みます。これにより(マルチラインモードであれば)個々の1行を1つの単位として処理します。

マルチラインモードとシングルラインモードで大きく意味が変わりますので、利用しているプログラミング言語のデフォルトモードに注意して下さい。

(b) 特定のパターンで終了しない文字列

否定戻り読みを用いて、「$」が示す「文末」の直前に特定のパターンを含まない文字列をマッチングします。同じく、「.*」は任意に変更できます。

// PATTERNで終了しない文字列の表現 
^.*(<!PATTERN)$

(c) 特定のパターンを含まない文字列

こちは、否定先読みを応用したものです。パターンの出現位置を限定せず、文内に「特定の文字列を含まない」パターンを記述する方法です。

// PATTERNを含まない文字列
^(?!.*PATTERN).*$

カッコ内のサブパターンの最初に量指定子「*」を追加しました。量指定子「*」は0回か、1回以上の文字の連続でした。これで出現位置を問わずに否定先読みを行うことができます。

[補足] 否定先読み・戻り読みについて

否定先読み・戻り読みは、理解するまでやや混乱しやすいですので、補足解説を加えておきます。

否定先読み(Negative lookahead)は、「(?!」と、「)」で囲んだ中に、特定の正規表現(サブパターンと呼びます)を書き込むことで記述できます。

「否定先読み」の記述法

否定先読みの記述法

一方で、否定戻り読み(Negative lookbehind)は、「(?<!」「)」でサブパターンを囲みます。「否定後読み」と呼ばれることもあります。

「否定戻り読み」の記述法

否定戻り読みの記述方

「否定先読み・戻り読み」で注意が必要なのは、このカッコ内に記述されるパターン(=サブパターン)は、メインのマッチング検索の処理とは異なり、その処理の後に「消費(Consume)される」ことはなく、一時的な吟味(テスト)の目的にのみ利用されるという点です。

「消費されない」が意味するところにご注意下さい。これはすなわち、

  • サブパターンのマッチは、メイン処理のように後方参照することができない。
  • 言明条件をパスした後、メイン処理は、そのサブパターンの直前(直後)の位置から再開される。

という事です。

「否定先読み」では、メインの走査の位置よりも「前方」を吟味して、カッコ内の別のパターンへのマッチングを吟味します。これが「先読み」という名前の由縁となります。このようなメインの走査処理から独立した吟味(テスト)は「Assertion(言明)」と呼ばれます。一方で「否定戻り読み」では、メインの処理より「後方を」テストする事は、その名前からも想像できると思います。

別の観点では、「カッコ内のサブパターンにマッチしない文字列が見つかれば、そのマッチ文字列の直前の位置(先読みの場合)か、直後の位置(戻り読みの場合)を示す」といった「位置」を表す「アンカー」の表現のようにも捉えられます。

・・・のような説明だけでは、非常にわかりにくいですので、具体例を見てみます。

具体的な使用例

それでは、下記の例文(1行)を対象に、上記の否定先読み・戻り読みパターンを適応してみます。どこかで聞き覚えのある例文かもしれません。

否定の正規表現の例文

この例文に対して、いくつかの否定の言明を検証して見ます。

「おれは」で開始しない文字列

「『おれは』で開始しない1行」という正規表現を考えてみます。否定先読みを使った表現は下記のようになるはずです。

// 「おれは」で開始しない文字列
^(?!おれは).*$

「^」が行頭、「.(ドット)」は改行以外のあらゆる一文字、「.(ドット)」は、0回か1回以上の連続を表す量指定子です。上記は「直後に『おれは』が現れない行頭と、その直後から続く改行以外の文字の0回以上の連続」をマッチする正規表現です。

例文は、行頭に「おれは」が出現していますので、この上記の正規表現はマッチを見つけられません。「おれは」がサブパターンにマッチした時点で、この正規表現全体のマッチは不成立です。

否定先読み。行頭に「おれは」を含まない文字列のマッチング。

「私は」で開始しない文字列

さらに理解を深めるため、否定先読みのサブパターンを少しだけ変更し、同じ例文を適応してみます。「おれは」でなく、代わりに「私は」としてみます。

// 「私は」で開始しない文字列
^(?!私は).*$

例文は、明らかに行頭に「私は」が現れませんので、この正規表現全体はマッチを見つけることができます。サブパターン「私は」の否定のテストをパスした後、その後の処理が、直前のマッチ(=行頭)の直後から開始することに注意して下さい。つまり、マッチは「おれは海賊王になる男だ!」です。

行頭に「私は」を含まないのでマッチ。

「海賊王」を含まない文字列

さて、本記事の主題である「〜を含まない文字列」です。否定先読みのカッコ内を「.*海賊王」としてみます。これにより、サブパターンが「海賊王」もしくは「……海賊王」を表すようになります。

// 「海賊王」を含まない文字列
^(?!.*海賊王).*$

上記は読み解くと、「直後に『海賊王』か、もしくは『…..海賊王』が現れない行頭と、その直後に続く改行以外の文字の0回以上の連続」です。これにより、行内のいずれかの位置に「海賊王」が含まれていればマッチしない正規表現、すなわち「海賊王を含まない文字列」といった、否定の正規表現が完成します。

海賊王を含まない正規表現。マッチしない。

「.*」の働きにより、「海賊王」が行頭に現れても、行尾にあらわれても見つける事ができるのは、理解できると思います。

「海軍」を含まない文字列

確認のため、サブパターンを変更してみます。「海軍」であればどうでしょうか?

海軍を含まない正規表現。マッチする。

文字列中のどの位置にも「海軍」は現れませんので、全体の正規表現はマッチすることになります。これで「〜を含まない文字列」の正規表現が正しく動作している事が確かめられました。

参考:肯定先読みの利用例

ちなみに「(?=」 と「)」で囲まれる言明は「肯定先読み」と呼ばれます。

肯定先読みの記述法

肯定先読みを使えば、逆に「海賊王」を含む文字列だけマッチできます。ご参考まで。

肯定先読みによるマッチング例。

以上、否定先読みの「表現がマッチしない(はじめての)位置を探す」という振る舞いを利用した表現の例でした。言明のバリエーションは下記の4つのセットが代表的です。

名前表現詳細
否定先読み
(Negative lookahead)
(?!pattern)開始位置から前を呼んでpatternがマッチしなければテストはパス
否定後読み
(Negative lookbehind)
(?<!pattern)開始位置から後ろを呼んでpatternがマッチしなければテストはパス
肯定先読み
(Positive lookahead)
(?=pattern)開始位置から前を呼んでpatternがマッチすればテストはパス
肯定後読み
(Positive lookbehind)
(?<=pattern)開始位置から前を呼んでpatternがマッチすればテストはパス

上記の中でも、特に否定先読み・戻り読みは、利用すると非常に多様な正規表現が可能になります。ぜひマスターしてみて下さい。今回紹介した、否定先読み、否定戻り読みに関しては、こちらにも詳しくまとめています。

参考リンク

また、今回1行を対象にするように「^」「$」のアンカーを利用しました。一般的な正規表現については、こちらを御覧ください。

こちらは、文書全体を処理する時の表現です。

閉じる