あなたも、複雑な文字列操作に頭を抱えたことはありませんか?「もっと効率的に処理できないだろうか」と悩んだ経験はないでしょうか。多くのWebエンジニアが直面するこの問題に、正規表現は強力な解決策を提供します。しかし、その難解な構文に躊躇している方も少なくないはずです。本記事では、JavaScriptにおける正規表現の基礎から応用まで、実践的な20の例を通じて徹底的に解説します。これらのテクニックを習得すれば、あなたのコーディングスキルは劇的に向上するでしょう。さあ、正規表現の魔法を使いこなし、効率的で柔軟な文字列処理の世界へ飛び込みましょう!
正規表現の基礎
正規表現(Regular Expression)は、文字列のパターンを記述するための強力なツールです。JavaScriptでは、RegExp
オブジェクトまたはリテラル表記(/.../
)を使用して正規表現を作成できます。
正規表現の作成方法
// リテラル表記
const regexLiteral = /pattern/;
// RegExpオブジェクト
const regexObject = new RegExp('pattern');
どちらの方法も同じ結果を得られますが、リテラル表記の方がより簡潔で読みやすいため、多くの場合で推奨されます。
基本的なメタ文字
正規表現では、特別な意味を持つメタ文字を使用してパターンを記述します。以下に主要なメタ文字を紹介します:
.
: 任意の1文字にマッチ^
: 行の先頭にマッチ$
: 行の末尾にマッチ*
: 直前の文字の0回以上の繰り返しにマッチ+
: 直前の文字の1回以上の繰り返しにマッチ?
: 直前の文字の0回または1回の出現にマッチ\
: エスケープ文字(特殊文字を通常の文字として扱う)
文字クラス
文字クラスを使用すると、特定の文字セットにマッチさせることができます:
[abc]
: a, b, cのいずれかにマッチ[^abc]
: a, b, c以外の任意の文字にマッチ[a-z]
: aからzまでの小文字にマッチ[A-Z]
: AからZまでの大文字にマッチ[0-9]
: 0から9までの数字にマッチ
量指定子
量指定子を使用すると、パターンの繰り返し回数を指定できます:
{n}
: 直前の文字がちょうどn回繰り返す{n,}
: 直前の文字がn回以上繰り返す{n,m}
: 直前の文字がn回以上m回以下繰り返す
実践的な正規表現テクニック20選
それでは、実際のコーディングシーンで役立つ20の正規表現テクニックを見ていきましょう。
メールアドレスの検証
メールアドレスの基本的な形式を検証する正規表現:
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
console.log(emailRegex.test('user@example.com')); // true
console.log(emailRegex.test('invalid-email')); // false
この正規表現は、一般的なメールアドレスの形式をカバーしていますが、完全に厳密ではありません。実際のアプリケーションでは、より複雑な検証や、メールアドレスの実在性の確認が必要になる場合があります。
パスワード強度の検証
最低8文字、大文字小文字、数字、特殊文字を含むパスワードを検証する正規表現:
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
console.log(passwordRegex.test('Str0ngP@ssword')); // true
console.log(passwordRegex.test('weakpass')); // false
この正規表現は、先読み肯定表現((?=...)
)を使用して、パスワードが特定の条件を満たしているかをチェックしています。
URLの検証
基本的なURLの形式を検証する正規表現:
const urlRegex = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
console.log(urlRegex.test('https://www.example.com')); // true
console.log(urlRegex.test('invalid-url')); // false
この正規表現は、httpやhttpsで始まるURLや、www.から始まるドメイン名、様々なトップレベルドメインに対応しています。
日付形式の検証(YYYY-MM-DD)
YYYY-MM-DD形式の日付を検証する正規表現:
const dateRegex = /^\d{4}-([0]\d|1[0-2])-([0-2]\d|3[01])$/;
console.log(dateRegex.test('2023-05-15')); // true
console.log(dateRegex.test('2023-13-32')); // false
この正規表現は、年(4桁)、月(01-12)、日(01-31)の基本的な範囲をチェックしています。ただし、各月の正確な日数(例:2月は28または29日まで)までは検証していないので、より厳密な検証が必要な場合は追加のロジックが必要です。
電話番号の検証(日本の場合)
日本の電話番号形式を検証する正規表現:
const phoneRegex = /^(0\d{1,4}-?\d{1,4}-?\d{4})$/;
console.log(phoneRegex.test('03-1234-5678')); // true
console.log(phoneRegex.test('090-1234-5678')); // true
console.log(phoneRegex.test('1234-5678')); // false
この正規表現は、固定電話と携帯電話の一般的な形式に対応しています。ハイフンの有無も許容しています。
郵便番号の検証(日本の場合)
日本の郵便番号形式を検証する正規表現:
const postalCodeRegex = /^\d{3}-?\d{4}$/;
console.log(postalCodeRegex.test('123-4567')); // true
console.log(postalCodeRegex.test('1234567')); // true
console.log(postalCodeRegex.test('123-456')); // false
この正規表現は、3桁-4桁の形式と、ハイフンなしの7桁の形式両方に対応しています。
IPアドレスの検証
IPv4アドレスを検証する正規表現:
const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
console.log(ipv4Regex.test('192.168.0.1')); // true
console.log(ipv4Regex.test('256.1.2.3')); // false
この正規表現は、各オクテットが0から255の範囲内にあることを確認します。
HTMLタグの抽出
HTMLタグを抽出する正規表現:
const htmlString = '<p>This is a <strong>bold</strong> text.</p>';
const tagRegex = /<[^>]+>/g;
console.log(htmlString.match(tagRegex)); // ['<p>', '<strong>', '</strong>', '</p>']
この正規表現は、開始タグと終了タグの両方を抽出します。ただし、HTMLの解析には専用のパーサーを使用することが推奨されます。
文字列内の数値の抽出
文字列から数値を抽出する正規表現:
const text = 'The price is $19.99 and the quantity is 5';
const numberRegex = /-?\d+(\.\d+)?/g;
console.log(text.match(numberRegex)); // ['19.99', '5']
この正規表現は、整数と小数の両方を抽出します。マイナス記号も考慮されています。
キャメルケースからスネークケースへの変換
キャメルケースの文字列をスネークケースに変換する正規表現:
const camelToSnake = (str) => str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
console.log(camelToSnake('camelCaseString')); // 'camel_case_string'
この正規表現は大文字を検出し、アンダースコアと小文字に置き換えています。
文字列内の重複単語の検出
文字列内の連続する重複単語を検出する正規表現:
const text = 'The the quick brown fox jumps over the lazy dog dog';
const duplicateRegex = /\b(\w+)\s+\1\b/g;
console.log(text.match(duplicateRegex)); // ['the the', 'dog dog']
この正規表現は、キャプチャグループと後方参照を使用して、連続する同じ単語を検出します。
文字列の先頭と末尾の空白を削除
文字列の先頭と末尾の空白を削除する正規表現:
const trim = (str) => str.replace(/^\s+|\s+$/g, '');
console.log(trim(' Hello, World! ')); // 'Hello, World!'
この正規表現は、文字列の先頭(^
)または末尾($
)にある1つ以上の空白文字(\s+
)を削除します。
URLからクエリパラメータの抽出
URLからクエリパラメータを抽出する正規表現:
const url = 'https://example.com/page?name=John&age=30';
const paramRegex = /[?&]([^=]+)=([^&]*)/g;
const params = {};
url.replace(paramRegex, (_, key, value) => {
params[key] = decodeURIComponent(value);
});
console.log(params); // { name: 'John', age: '30' }
この正規表現は、URLのクエリ文字列から各パラメータのキーと値を抽出します。
文字列内のHTMLエンティティのエスケープ
特殊文字をHTMLエンティティにエスケープする正規表現:
const escapeHTML = (str) => str.replace(/[&<>"']/g, (m) => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
console.log(escapeHTML('<p>"Hello & World!"</p>')); // '<p>"Hello & World!"</p>'
この正規表現は、HTML内で特別な意味を持つ文字をエスケープします。
文字列内の特定の単語の置換
文字列内の特定の単語を別の単語に置換する正規表現:
const text = 'The quick brown fox jumps over the lazy dog';
const replaceWord = (str, oldWord, newWord) => str.replace(new RegExp(`\\b${oldWord}\\b`, 'gi'), newWord);
console.log(replaceWord(text, 'fox', 'cat')); // 'The quick brown cat jumps over the lazy dog'
この正規表現は、単語の境界(\b
)を使用して、完全な単語のみを置換します。gi
フラグにより、大文字小文字を区別せずにグローバルに置換を行います。
カスタム日付フォーマットの解析
カスタムな日付フォーマットを解析する正規表現:
const parseDate = (dateString, format) => {
const parts = {};
const regex = format.replace(/[YMD]/g, (match) => {
const regexPart = {
'Y': '(\\d{4})',
'M': '(\\d{1,2})',
'D': '(\\d{1,2})'
}[match];
return `(?<${match}>${regexPart})`;
});
const match = dateString.match(new RegExp(regex));
if (match) {
return new Date(match.groups.Y, parseInt(match.groups.M) - 1, match.groups.D);
}
return null;
};
console.log(parseDate('2023/05/15', 'Y/M/D')); // Mon May 15 2023 ...
console.log(parseDate('15-05-2023', 'D-M-Y')); // Mon May 15 2023 ...
この例では、動的に正規表現を構築し、名前付きキャプチャグループを使用して日付の各部分を抽出しています。
文字列内の数式の計算
文字列内の簡単な数式を計算する正規表現:
const calculateExpression = (expr) => {
const regex = /(-?\d+(?:\.\d+)?)\s*([\+\-\*\/])\s*(-?\d+(?:\.\d+)?)/;
while (regex.test(expr)) {
expr = expr.replace(regex, (_, a, op, b) => {
const result = {
'+': (x, y) => x + y,
'-': (x, y) => x - y,
'*': (x, y) => x * y,
'/': (x, y) => x / y
}[op](parseFloat(a), parseFloat(b));
return result.toString();
});
}
return expr;
};
console.log(calculateExpression('5 + 3 * 2')); // '11'
console.log(calculateExpression('10 / 2 - 3')); // '2'
この例では、正規表現を使用して数式の各部分(数値と演算子)を抽出し、順番に計算しています。ただし、この方法は演算子の優先順位を考慮していないため、複雑な数式の場合は適切ではありません。
文字列内のカラーコードの抽出
文字列内のHEXカラーコードを抽出する正規表現:
const extractColorCodes = (text) => {
const colorRegex = /#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})\b/g;
return text.match(colorRegex) || [];
};
const text = 'The color #FF5733 is vibrant, while #000 is black.';
console.log(extractColorCodes(text)); // ['#FF5733', '#000']
この正規表現は、6桁または3桁のHEXカラーコードを抽出します。
文字列内のMarkdownリンクの解析
Markdownリンクを解析する正規表現:
const parseMarkdownLinks = (markdown) => {
const linkRegex = /\[([^\]]+)\]\(([^\)]+)\)/g;
const links = [];
let match;
while ((match = linkRegex.exec(markdown)) !== null) {
links.push({ text: match[1], url: match[2] });
}
return links;
};
const markdown = 'Check out [Google](https://www.google.com) and [GitHub](https://github.com).';
console.log(parseMarkdownLinks(markdown));
// [
// { text: 'Google', url: 'https://www.google.com' },
// { text: 'GitHub', url: 'https://github.com' }
// ]
この正規表現は、Markdownリンクの形式([テキスト](URL)
)を解析し、リンクテキストとURLを抽出します。
文字列内の時間表現の標準化
さまざまな時間表現を24時間形式に標準化する正規表現:
const standardizeTime = (timeString) => {
const timeRegex = /(\d{1,2})(?::(\d{2}))?\s*(am|pm)?/i;
const match = timeString.match(timeRegex);
if (match) {
let [_, hours, minutes = '00', ampm] = match;
hours = parseInt(hours);
if (ampm) {
if (ampm.toLowerCase() === 'pm' && hours < 12) hours += 12;
if (ampm.toLowerCase() === 'am' && hours === 12) hours = 0;
}
return `${hours.toString().padStart(2, '0')}:${minutes}`;
}
return null;
};
console.log(standardizeTime('3:30pm')); // '15:30'
console.log(standardizeTime('11 am')); // '11:00'
console.log(standardizeTime('14:45')); // '14:45'
この正規表現は、12時間形式と24時間形式の両方を解析し、24時間形式に標準化します。
正規表現のパフォーマンスと最適化
正規表現は強力なツールですが、不適切な使用はパフォーマンスの低下を招く可能性があります。以下に、正規表現のパフォーマンスを向上させるためのいくつかのヒントを紹介します。
過度な後方参照を避ける
後方参照(\1
, \2
など)の過度な使用は、正規表現エンジンに大きな負荷をかける可能性があります。可能な限り、名前付きキャプチャグループ((?<name>...)
)を使用することを検討してください。
貪欲な量指定子の使用を最小限に
*
や+
などの貪欲な量指定子の使用は、バックトラッキングを引き起こし、パフォーマンスを低下させる可能性があります。可能な場合は、非貪欲な量指定子(*?
や+?
)を使用するか、より具体的なパターンを記述してください。
適切なアンカーの使用
^
(行頭)や$
(行末)などのアンカーを適切に使用することで、不要なマッチングを避け、パフォーマンスを向上させることができます。
過度に複雑な正規表現を避ける
1つの複雑な正規表現よりも、複数の単純な正規表現を組み合わせて使用する方が、多くの場合パフォーマンスが向上します。
正規表現オブジェクトの再利用
頻繁に使用する正規表現は、オブジェクトとして一度だけ作成し、再利用することをお勧めします。
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
// この正規表現オブジェクトを繰り返し使用する
正規表現のテストとデバッグ
正規表現のテストとデバッグは、複雑なパターンを扱う際に非常に重要です。以下に、役立つツールとテクニックを紹介します。
オンラインツールの活用
JavaScriptコンソールの使用
ブラウザの開発者ツールやNode.jsのREPLを使用して、正規表現をインタラクティブにテストできます。
> /hello/.test('hello world')
true
> 'hello world'.match(/\w+/g)
['hello', 'world']
ユニットテストの作成
正規表現を使用するコードには、必ずユニットテストを書くことをお勧めします。Jestなどのテストフレームワークを使用して、さまざまな入力に対する正規表現の動作を確認できます。
test('email validation', () => {
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
expect(emailRegex.test('user@example.com')).toBe(true);
expect(emailRegex.test('invalid-email')).toBe(false);
});
まとめ
JavaScriptにおける正規表現は、文字列操作と検証のための強力なツールです。本記事で紹介した20のテクニックは、日々のコーディングタスクを効率化し、よりクリーンで堅牢なコードを書く助けとなるでしょう。正規表現の学習曲線は急かもしれませんが、その威力を理解し、適切に使用できるようになれば、プログラミングの可能性が大きく広がります。
ただし、正規表現は万能ではありません。複雑すぎる正規表現は、コードの可読性を損ない、メンテナンスを困難にする可能性があります。また、HTMLやXMLの解析など、特定のタスクには専用のパーサーを使用することが推奨されます。
正規表現の学習は継続的なプロセスです。新しいパターンや技術を学び、実践し、そして他の開発者と知識を共有することで、あなたのスキルは着実に向上していくでしょう。
最後に、正規表現を使用する際は、常にセキュリティを意識することが重要です。ユーザー入力を直接正規表現に使用する場合は、適切なサニタイズを行い、潜在的な脆弱性を防ぐようにしてください。
Happy coding!
コメント