正規表現で「〜以外」を書く方法 - 否定先読み・文字クラス除外
「正規表現で特定の文字以外にマッチしたい」「ある文字列を含まない行だけを探したい」場合、 対象が1文字か文字列かで書き方が変わります。 正規表現の否定には、否定文字クラス・否定先読み・否定後読みを使い分けます。 このページの例はRegex Testerで実際に試せます。
Regex has different ways to express "not" depending on whether you need to exclude one character or reject a whole string. Use a negated character class, negative lookahead, or negative lookbehind according to which position and text you need to test.
1. 1文字だけ除外する: 否定文字クラス [^abc]
/[^abc]/g文字クラスの先頭に ^ を置くと、その集合に含まれない1文字に マッチします。[^abc] は「a、b、c以外の1文字」という意味です。abcXYZ なら、X、Y、Z がそれぞれマッチします。 正規表現で特定の文字以外を探すときの基本形です。
// 数字以外の1文字
/[^0-9]/g
// スラッシュ以外が1文字以上続く部分
/[^/]+/g2. ドット . と否定文字クラスの違い
/. // 通常は改行以外の任意の1文字
/[^abc] // a、b、c以外の任意の1文字ドット . は通常、改行以外の任意の1文字にマッチします。一方、[^abc] は改行を含めて「指定した文字以外」を表します。 JavaScriptでは s(dotAll)フラグ付きのドットは改行にもマッチします。 「何でもよい」ならドット、「この文字だけ除く」なら否定文字クラスです。
3. 特定の文字列を含まない: 否定先読み (?!pattern)
/^(?!.*foo).*$/gmこのパターンは foo を含まない行だけにマッチします。行頭 ^ で 否定先読み (?!.*foo) を評価し、行内に foo がないことを確認してから、.*$ で行全体を取得します。大文字・小文字を同一視するなら iフラグを追加します。正規表現で特定の文字列を含まない条件や、検索結果から文字列を除く 条件には、文字クラスではなく否定先読みが適しています。
// .tmp で終わらないファイル名
/^(?!.*\.tmp$).+$/
// error を含まない行(大文字・小文字を無視)
/^(?!.*error).*$/gim4. 特定の文字列の後ろに続かない: 否定後読み (?<!pattern)
/(?<!USD)(?<!\d)\d+/g否定後読み (?<!USD) は、現在位置の直前が USD ではない場合だけ マッチを許可します。ただし\b(単語境界)は英字と数字を区別しないため、USD100のように英字と数字が地続きの場合はDと1の間に 境界が生まれず、\b\d+\bでは数字に一致できません。数字の先頭であることを保証するには(?<!\d)(直前が数字でない)を追加します。この形ならUSD100 JPY200 で USD の直後の 100 を除き、200 にだけマッチします。 「直後に特定の文字列がない」を見る否定先読みとは、見る方向が逆です。
5. 否定先読み・否定後読みのブラウザ対応
否定先読みは主要ブラウザで長く対応されています。否定後読みは比較的新しく、 Safariでは16.4以降で対応しました。古いブラウザでは、キャプチャやJavaScript側の 条件判定に置き換える選択肢もあります。詳しくは正規表現の後読みとブラウザ対応を参照してください。
複数行テキストでの否定:mフラグの有無で挙動が変わる
/^(?!.*foo).*$/ を複数行のテキストに使う場合、m(multiline)フラグを 忘れると意図通りに動きません。mフラグが無いと ^ は文字列全体の先頭、$ は文字列全体の末尾にしかマッチせず、1行ずつの判定になりません。
const text = "line1 ok\nline2 has foo\nline3 ok";
// ❌ mフラグなし: ^と$が文字列全体の先頭・末尾を指すため、1行目しか判定されない
text.match(/^(?!.*foo).*$/g);
// ✅ mフラグあり: ^と$が各行の先頭・末尾を指し、行ごとに判定される
text.match(/^(?!.*foo).*$/gm); // ["line1 ok", "line3 ok"]逆に、改行を含む文字列全体を1つのかたまりとして「どこにもfooが無いこと」を確認したい場合は、mフラグではなくs(dotAll)フラグや[\s\S]*を使い、.が改行もまたぐようにします。「行ごとの判定」か「全体の判定」かで、 必要なフラグが変わる点を混同しないようにしてください。
実務でよく使う否定パターン集
| やりたいこと / Goal | パターン |
|---|---|
| .tmp / .log 以外のファイル名 | /^(?!.*\.(tmp|log)$).+$/ |
| #で始まるコメント行を除外 | /^(?!\s*#).+$/gm |
| HTMLタグを含まない行 | /^(?!.*<[^>]+>).*$/gm |
| example.comドメイン以外のURL | /https?:\/\/(?!example\.com)\S+/g |
いずれも「1文字の除外」ではなく「文字列・行単位の除外」なので、[^...]ではなく 否定先読みを使う場面です。実際のデータで試す際はRegex Testerで テスト文字列を差し替えながら確認してください。
よくある間違い
- [^abc]で文字列abcを除外したつもりになる:
[^abc]は 「文字列abc以外」ではなく、「a、b、cのどれでもない1文字」です。複数文字の除外には 否定先読みを使います。 - 否定先読みを置く位置が違う: 否定先読みは「その位置から見て」続く文字列を 判定します。行全体に
fooがないか調べるなら、^の直後に(?!.*foo)を置きます。 - 先読み自体が文字を取得すると思う: 先読み・後読みは条件を確認するだけで、 文字を消費しないゼロ幅アサーションです。取得部分はその後の
.*や\d+などで指定します。 - ドットが改行をまたぐと思い込む:
.は通常、改行にマッチしません。 複数行全体ならsフラグや[\s\S]を検討してください。
Regex Testerで試す
Regex Testerにパターンとテスト文字列を貼り付けると、 マッチ箇所をすぐ確認できます。否定先読みの位置や m、i、s フラグを切り替え、期待する行だけが残るか比較してみてください。
English summary
Use [^abc] to match one character other than a, b, orc; it does not exclude the string abc. To match a line that does not contain foo, use ^(?!.*foo).*$. Negative lookahead(?!pattern) checks what does not follow the current position, while negative lookbehind (?<!pattern) checks what does not precede it. Both are zero-width assertions. Lookbehind is newer and requires Safari 16.4 or later.