- Authors

- Name
- Youngju Kim
- @fjvbn20031
1. なぜ正規表現(せいきひょうげん)は今(いま)も重要(じゅうよう)か
2025年においても、正規表現(Regular Expression, regex)は開発者の核心(かくしん)ツールです。
入力検証(にゅうりょくけんしょう) — メール、URL、電話番号、クレジットカード番号のフォーマットを検証する最も簡潔な方法です。
ログ分析(ぶんせき) — 数GBのログファイルから特定パターン(エラーコード、IPアドレス、タイムスタンプ)を抽出します。
テキスト変換(へんかん) — コードリファクタリング、データクレンジング、ファイルフォーマット変換に活用されます。
AIプロンプト前処理 — LLMに入力する前に不要な特殊文字やHTMLタグを除去するために使用されます。
すべての言語でサポート — JavaScript、Python、Java、Go、Rustなど、ほぼすべてのプログラミング言語に組み込まれています。
2. 基礎文法(きそぶんぽう):リテラルとメタ文字
リテラルマッチング
正規表現の最も基本はリテラルマッチングです。文字をそのままマッチします。
import re
# リテラルマッチング
print(re.findall(r'hello', 'hello world hello'))
# ['hello', 'hello']
核心メタ文字
| メタ文字 | 意味 | 例 | マッチ結果 |
|---|---|---|---|
. | 任意の1文字(改行除く) | h.t | hat, hit, hot |
^ | 文字列の先頭 | ^Hello | "Hello world"のHello |
$ | 文字列の末尾 | world$ | "Hello world"のworld |
* | 0回以上の繰り返し | ab*c | ac, abc, abbc |
+ | 1回以上の繰り返し | ab+c | abc, abbc(acは不可) |
? | 0回または1回 | colou?r | color, colour |
| | OR(または) | cat|dog | catまたはdog |
[] | 文字クラス | [aeiou] | 母音1つ |
() | グループ化/キャプチャ | (ab)+ | ab, abab |
\ | エスケープ | \. | 実際のピリオド |
エスケープが必要な文字
以下のメタ文字をリテラルとして使用するには、\を前に付ける必要があります:
. * + ? ^ $ | [ ] ( ) { } \
# ピリオドをリテラルとしてマッチ
print(re.findall(r'www\.example\.com', 'visit www.example.com'))
# ['www.example.com']
3. 文字クラス(Character Classes)
基本文字クラス
# [abc] — a, b, cのいずれか
re.findall(r'[aeiou]', 'hello world')
# ['e', 'o', 'o']
# [^abc] — a, b, c以外の文字
re.findall(r'[^aeiou ]', 'hello world')
# ['h', 'l', 'l', 'w', 'r', 'l', 'd']
# [a-z] — 範囲指定
re.findall(r'[A-Z]', 'Hello World')
# ['H', 'W']
# [0-9a-fA-F] — 16進数文字
re.findall(r'[0-9a-fA-F]+', 'color: #FF00AB')
# ['FF00AB']
省略文字クラス
| 省略 | 同等の表現 | 意味 |
|---|---|---|
\d | [0-9] | 数字 |
\D | [^0-9] | 数字以外 |
\w | [a-zA-Z0-9_] | 単語文字 |
\W | [^a-zA-Z0-9_] | 単語文字以外 |
\s | [ \t\n\r\f\v] | 空白文字 |
\S | [^ \t\n\r\f\v] | 空白以外 |
# 電話番号の抽出
text = "電話: 03-1234-5678, FAX: 06-123-4567"
print(re.findall(r'\d{2,3}-\d{3,4}-\d{4}', text))
# ['03-1234-5678', '06-123-4567']
POSIX文字クラス(一部の言語)
[:alpha:] — アルファベット文字
[:digit:] — 数字
[:alnum:] — アルファベット + 数字
[:space:] — 空白文字
[:upper:] — 大文字
[:lower:] — 小文字
[:punct:] — 句読点
POSIXクラスはgrep、sedなどUNIXツールで主に使用されます。Python/JavaScriptでは\d、\w等を使います。
4. 量指定子(りょうしていし)(Quantifiers)
基本量指定子
# * (0回以上)
re.findall(r'go*d', 'gd god good goood')
# ['gd', 'god', 'good', 'goood']
# + (1回以上)
re.findall(r'go+d', 'gd god good goood')
# ['god', 'good', 'goood']
# ? (0または1回)
re.findall(r'colou?r', 'color colour')
# ['color', 'colour']
正確な回数指定
# {n} — 正確にn回
re.findall(r'\d{4}', '2025 is the year 12345')
# ['2025', '1234']
# {n,m} — n回以上m回以下
re.findall(r'\d{2,4}', '1 12 123 1234 12345')
# ['12', '123', '1234', '1234']
# {n,} — n回以上
re.findall(r'\d{3,}', '1 12 123 1234')
# ['123', '1234']
貪欲(どんよく)vs 怠惰(たいだ)マッチング
デフォルトでは量指定子は貪欲です — 可能な限り多くマッチします。?を後ろに付けると怠惰モードになります。
text = '<b>bold</b> and <i>italic</i>'
# 貪欲(デフォルト)
print(re.findall(r'<.*>', text))
# ['<b>bold</b> and <i>italic</i>'] — 全体を一つにマッチ!
# 怠惰(?を追加)
print(re.findall(r'<.*?>', text))
# ['<b>', '</b>', '<i>', '</i>'] — 各タグを個別にマッチ
所有的(しょゆうてき)量指定子
所有的量指定子(*+、++、?+)はバックトラッキングしません。Java、PCREでサポートされます(Python標準reではサポートなし、regexモジュールでサポート)。
// Java例
// 所有的量指定子 — バックトラッキングなし
String pattern = "a++b"; // 'a'を最大限消費後、戻らない
5. グループ(Groups)
キャプチャグループ
# 基本キャプチャグループ
text = "2025-03-23"
match = re.match(r'(\d{4})-(\d{2})-(\d{2})', text)
if match:
print(match.group(0)) # '2025-03-23'(全体マッチ)
print(match.group(1)) # '2025'(年)
print(match.group(2)) # '03'(月)
print(match.group(3)) # '23'(日)
非キャプチャグループ
(?:...) — グループ化するがキャプチャしません。パフォーマンス上の利点があります。
# 非キャプチャグループ
text = "http://example.com https://secure.com"
print(re.findall(r'(?:https?://)(\S+)', text))
# ['example.com', 'secure.com'] — ドメインのみキャプチャ
名前付きグループ
# 名前付きグループ
text = "2025-03-23"
match = re.match(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})', text)
if match:
print(match.group('year')) # '2025'
print(match.group('month')) # '03'
print(match.group('day')) # '23'
後方参照(こうほうさんしょう)
# 重複する単語を検出
text = "the the quick brown fox fox"
print(re.findall(r'\b(\w+)\s+\1\b', text))
# ['the', 'fox']
# HTMLタグのマッチ(同じタグ名)
html = "<div>content</div> <span>text</span>"
print(re.findall(r'<(\w+)>.*?</\1>', html))
# ['div', 'span']
6. アンカーと境界(きょうかい)
基本アンカー
# ^ — 文字列の先頭
print(re.findall(r'^Hello', 'Hello World\nHello Again'))
# ['Hello'] — 最初のみ
# $ — 文字列の末尾
print(re.findall(r'end$', 'start to end'))
# ['end']
# マルチラインモード
print(re.findall(r'^Hello', 'Hello World\nHello Again', re.MULTILINE))
# ['Hello', 'Hello'] — 各行の先頭
単語境界(\b)
\bは単語文字と非単語文字の間の位置をマッチします。ゼロ幅で文字を消費しません。
text = "cat catfish concatenate scattered"
# \bなし
print(re.findall(r'cat', text))
# ['cat', 'cat', 'cat', 'cat']
# \bで正確な単語のみ
print(re.findall(r'\bcat\b', text))
# ['cat']
# 単語の先頭のみ
print(re.findall(r'\bcat\w*', text))
# ['cat', 'catfish', 'concatenate']
7. 先読み(さきよみ)と後読み(あとよみ)
先読みと後読みはゼロ幅アサーション(Zero-width Assertions) です。文字を消費せずに条件のみ確認します。
4つのタイプ
| タイプ | 構文 | 意味 |
|---|---|---|
| 肯定先読み | (?=...) | 後ろに...がある位置 |
| 否定先読み | (?!...) | 後ろに...がない位置 |
| 肯定後読み | (?<=...) | 前に...がある位置 |
| 否定後読み | (?<!...) | 前に...がない位置 |
実用例:パスワード検証(けんしょう)
def validate_password(password):
"""
パスワードルール:
- 8文字以上
- 大文字最低1つ
- 小文字最低1つ
- 数字最低1つ
- 特殊文字最低1つ
"""
pattern = r'^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$'
return bool(re.match(pattern, password))
print(validate_password("Abc123!@")) # True
print(validate_password("abc123")) # False(大文字・特殊文字なし)
print(validate_password("Short1!")) # False(8文字未満)
数値フォーマット(後読み活用)
# 数値にカンマ追加(千の位区切り)
def format_number(n):
return re.sub(r'(?<=\d)(?=(\d{3})+(?!\d))', ',', str(n))
print(format_number(1234567890))
# '1,234,567,890'
否定先読みの活用
# .jsファイルだが.min.jsではないものを検索
files = ["app.js", "app.min.js", "utils.js", "vendor.min.js"]
pattern = r'^(?!.*\.min\.js$).*\.js$'
for f in files:
if re.match(pattern, f):
print(f)
# app.js
# utils.js
8. 実践パターン30選
メール関連
# 1. 基本メール検証
email_basic = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
# 2. メールからドメイン抽出
email_domain = r'@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})'
# 3. Gmailアドレスのみマッチ
gmail_only = r'^[a-zA-Z0-9._%+-]+@gmail\.com$'
URL関連
# 4. 基本URLマッチ
url_basic = r'https?://[a-zA-Z0-9.-]+(?:/[^\s]*)?'
# 5. URLからドメイン抽出
url_domain = r'https?://([^/\s]+)'
# 6. クエリパラメータ抽出
query_param = r'[?&]([^=&]+)=([^&]*)'
IPアドレス
# 7. IPv4アドレス
ipv4 = r'\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b'
# 8. プライベートIPアドレス
private_ip = r'\b(?:10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3})\b'
電話番号
# 9. 日本の携帯電話
jp_mobile = r'0[789]0-?\d{4}-?\d{4}'
# 10. 日本の固定電話
jp_phone = r'0\d{1,4}-?\d{1,4}-?\d{4}'
# 11. 国際電話番号(E.164)
intl_phone = r'\+\d{1,3}\d{4,14}'
日付/時刻
# 12. YYYY-MM-DD
date_iso = r'\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])'
# 13. 多様な日付形式
date_multi = r'\d{4}[/.-]\d{1,2}[/.-]\d{1,2}'
# 14. 24時間制
time_24h = r'(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d)?'
# 15. ISO 8601日時
iso_datetime = r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})'
開発関連
# 16. HTMLタグ
html_tag = r'<\/?[a-zA-Z][a-zA-Z0-9]*(?:\s[^>]*)?\/?>'
# 17. HEXカラーコード
hex_color = r'#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})\b'
# 18. セマンティックバージョニング(SemVer)
semver = r'\bv?(\d+)\.(\d+)\.(\d+)(?:-([\w.]+))?(?:\+([\w.]+))?\b'
# 19. JSONキー
json_key = r'"([^"\\]*)"\s*:'
# 20. CSSクラスセレクタ
css_class = r'\.[a-zA-Z_][\w-]*'
日本語/韓国語/CJK
# 21. ひらがな
hiragana = r'[\u3040-\u309F]+'
# 22. カタカナ
katakana = r'[\u30A0-\u30FF]+'
# 23. 漢字(CJK統合漢字)
kanji = r'[\u4E00-\u9FFF]+'
# 24. 韓国語(ハングル)
hangul = r'[\uAC00-\uD7AF]+'
# 25. すべてのCJK文字
cjk_all = r'[\u3000-\u9FFF\uAC00-\uD7AF]+'
セキュリティ/検証
# 26. 強力なパスワード
strong_password = r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$'
# 27. SQLインジェクション疑いパターン
sql_injection = r"(?i)(?:union\s+select|or\s+1\s*=\s*1|drop\s+table|insert\s+into)"
# 28. XSS疑いパターン
xss_pattern = r'(?i)<script[^>]*>.*?</script>'
その他便利なパターン
# 29. 空白の正規化(連続空白を1つに)
text = "too many spaces"
print(re.sub(r'\s+', ' ', text))
# 'too many spaces'
# 30. CSVフィールド抽出(カンマ区切り、引用符サポート)
csv_field = r'(?:"([^"]*)"|([^,]*))'
9. 言語別(げんごべつ)の正規表現の違い
JavaScript
// リテラル構文
const re1 = /hello/gi;
// コンストラクタ構文
const re2 = new RegExp('hello', 'gi');
// 主要メソッド
'hello world'.match(/hello/); // ['hello']
'hello world'.replace(/hello/, 'hi'); // 'hi world'
/hello/.test('hello world'); // true
// Named Groups(ES2018+)
const match = '2025-03-23'.match(
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
);
console.log(match.groups.year); // '2025'
// matchAll(ES2020+)
const text = 'cat bat sat';
for (const m of text.matchAll(/[a-z]at/g)) {
console.log(m[0], m.index);
}
// Lookbehind(ES2018+)
'$100 200 $300'.match(/(?<=\$)\d+/g); // ['100', '300']
Python
import re
# コアメソッド
re.match(r'^hello', 'hello world') # 先頭でのみマッチ
re.search(r'hello', 'say hello') # どこでもマッチ
re.findall(r'\d+', 'a1 b2 c3') # 全マッチのリスト
re.sub(r'\d+', 'N', 'a1 b2') # 置換: 'aN bN'
# コンパイル(繰り返し使用時にパフォーマンス向上)
pattern = re.compile(r'\d{3}-\d{4}')
pattern.findall('03-1234-5678')
# フラグ
re.IGNORECASE # 大文字小文字を無視
re.MULTILINE # ^と$を各行に適用
re.DOTALL # .が改行もマッチ
re.VERBOSE # コメントと空白を許可
# VERBOSEの例
phone_re = re.compile(r'''
(\d{2,4}) # 市外局番
[-.\s]? # 区切り文字
(\d{3,4}) # 中間番号
[-.\s]? # 区切り文字
(\d{4}) # 末尾番号
''', re.VERBOSE)
Java
import java.util.regex.*;
// 基本的な使用法
Pattern pattern = Pattern.compile("\\d{4}-\\d{2}-\\d{2}");
Matcher matcher = pattern.matcher("Date: 2025-03-23");
if (matcher.find()) {
System.out.println(matcher.group()); // "2025-03-23"
}
// Named Groups
Pattern datePattern = Pattern.compile(
"(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})"
);
Matcher m = datePattern.matcher("2025-03-23");
if (m.find()) {
System.out.println(m.group("year")); // "2025"
}
// Stringメソッド
"hello world".matches("hello.*"); // true
"a1b2c3".replaceAll("\\d", "N"); // "aNbNcN"
"a,b,,c".split(",", -1); // ["a", "b", "", "c"]
Go
package main
import (
"fmt"
"regexp"
)
func main() {
// コンパイル
re := regexp.MustCompile(`\d{4}-\d{2}-\d{2}`)
// マッチ確認
fmt.Println(re.MatchString("2025-03-23")) // true
// 検索
fmt.Println(re.FindString("Date: 2025-03-23")) // "2025-03-23"
// すべて検索
fmt.Println(re.FindAllString("2025-03-23 and 2025-12-31", -1))
// Named Groups
re2 := regexp.MustCompile(
`(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})`,
)
match := re2.FindStringSubmatch("2025-03-23")
for i, name := range re2.SubexpNames() {
if name != "" {
fmt.Printf("%s: %s\n", name, match[i])
}
}
}
言語別機能比較
| 機能 | JavaScript | Python | Java | Go |
|---|---|---|---|---|
| Lookahead | O(ES2018+) | O | O | X |
| Lookbehind | O(ES2018+) | O | O | X |
| Named Groups | O(ES2018+) | O | O | O |
| Possessive | X | X(regexモジュール) | O | X |
| Atomic Groups | X | X(regexモジュール) | O | O |
| Unicodeカテゴリ | O | O | O | O |
| 再帰パターン | X | X(regexモジュール) | X | X |
| VERBOSEモード | X | O | O(COMMENTS) | X |
10. パフォーマンス最適化とReDoS防止(ぼうし)
バックトラッキングの理解
正規表現エンジン(NFA)はマッチ失敗時にバックトラッキングで別の経路を試みます。
# 通常のバックトラッキング
# パターン: a*b
# 入力: "aaac"
# 試行: "aaa" + b? 失敗 → "aa" + b? 失敗 → "a" + b? 失敗 → "" + b? 失敗
# 合計4回のバックトラック — 問題なし
壊滅的(かいめつてき)バックトラッキング
import time
# 危険なパターン!
dangerous_pattern = r'(a+)+b'
safe_pattern = r'a+b'
# 短い入力 — 両方とも速い
text_short = 'a' * 10 + 'c'
# 長い入力 — 危険なパターンは指数的な時間がかかる
text_long = 'a' * 25 + 'c'
start = time.time()
re.match(safe_pattern, text_long)
print(f"安全なパターン: {time.time() - start:.4f}s")
# 注意: 以下のコードは非常に長い時間がかかる可能性があります!
# start = time.time()
# re.match(dangerous_pattern, text_long)
# print(f"危険なパターン: {time.time() - start:.4f}s")
ReDoS脆弱パターン一覧
# 1. ネストされた量指定子
r'(a+)+' # 危険!
r'(a*)*' # 危険!
r'(a+)*' # 危険!
# 2. 重複する選択肢
r'(a|a)+' # 危険!
r'(\d+|\d+\.)+' # 危険!
# 3. 安全な代替パターン
r'a+' # (a+)+の代わり
r'a*' # (a*)*の代わり
r'\d+\.?' # (\d+|\d+\.)+の代わり
ReDoS防止ガイドライン
- ネストされた量指定子を禁止 —
(a+)+、(a*)*パターンを避ける - 選択肢の重複を排除 — OR選択肢が同じ文字をマッチしないようにする
- アトミックグループを使用 — サポートするエンジンで
(?>...)を使う - 入力長を制限 — 正規表現適用前に入力長を検証する
- タイムアウトを設定 — 正規表現実行に時間制限を設ける
# Pythonでのタイムアウト設定
import signal
def timeout_handler(signum, frame):
raise TimeoutError("Regex timeout!")
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(1) # 1秒タイムアウト
try:
re.match(r'(a+)+b', 'a' * 30 + 'c')
except TimeoutError:
print("正規表現の実行がタイムアウトしました。")
finally:
signal.alarm(0)
11. ツール活用
regex101.com
オンライン正規表現テスターとして最も人気のあるツールです。
主な機能:
- リアルタイムマッチハイライト
- パターン説明の自動生成
- JavaScript、Python、Go、Javaなど多様なエンジンをサポート
- マッチプロセスのステップバイステップデバッガー
- パターンの保存と共有
grepとripgrep
# grepの基本使用
grep -E 'error|warning' /var/log/syslog
grep -P '(?<=user:)\w+' access.log # Perl互換正規表現
# ripgrep (rg) — より高速な代替
rg 'TODO|FIXME' --type py
rg '\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b' access.log
rg -e 'pattern1' -e 'pattern2' . # 複数パターン
sedとawk
# sed — ストリームエディタ
# 日付形式の変換: 2025/03/23 → 2025-03-23
echo "2025/03/23" | sed 's/\//-/g'
# メールアドレスのマスキング
echo "user@example.com" | sed 's/\(.\).*@/\1***@/'
# u***@example.com
# awk — パターンマッチ + 処理
# 200ステータスコードのログのみ出力
awk '/HTTP\/[0-9.]+" 200/' access.log
# IP別リクエスト数の集計
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head
IDE活用
ほとんどのIDE(VS Code、IntelliJ、Vimなど)で正規表現の検索/置換をサポートしています。
VS Codeのヒント:
Ctrl+H(またはCmd+H)で置換モード- 正規表現アイコン(
.*)をクリックして有効化 - キャプチャグループは
$1、$2で後方参照
12. 面接(めんせつ)質問10選
Q1. .*と.*?の違いは?
.*は貪欲(greedy)— 可能な限り多くの文字をマッチします。.*?は怠惰(lazy)— 可能な限り少ない文字をマッチします。
Q2. ^と$はマルチラインモードでどう変わりますか?
デフォルトモードでは^は文字列の先頭、$は文字列の末尾のみをマッチします。マルチラインモード(re.MULTILINE)では各行の先頭と末尾をマッチします。
Q3. 先読み(Lookahead)と後読み(Lookbehind)の違いは?
先読み(?=...)は現在位置の後のパターンを確認し、後読み(?<=...)は現在位置の前のパターンを確認します。どちらもゼロ幅アサーションで文字を消費しません。
Q4. ReDoSとは何で、どう防止しますか?
ReDoSは非効率的な正規表現が指数的なバックトラッキングを引き起こし、CPUを過度に使用する攻撃です。ネストされた量指定子を避け、入力長を制限し、タイムアウトを設定して防止します。
Q5. キャプチャグループと非キャプチャグループの違いは?
キャプチャグループ(...)はマッチした文字列を保存して後で参照できます。非キャプチャグループ(?:...)はグループ化のみで保存せず、パフォーマンスがわずかに良くなります。
Q6. \bの役割は?
単語境界(word boundary)をマッチします。単語文字(\w)と非単語文字の間の位置、または文字列の先頭/末尾と単語文字の間をマッチします。
Q7. メール検証に正規表現を使用する際の限界は?
RFC 5322標準を完全に実装するには非常に複雑なパターンが必要です。実務では基本的な形式検証のみ正規表現で行い、実際の存在確認は確認メール送信で行います。
Q8. Pythonのre.matchとre.searchの違いは?
re.matchは文字列の先頭でのみマッチを試み、re.searchは文字列全体で最初のマッチを検索します。
Q9. 正規表現でバックスラッシュをマッチするには?
パターンで\\\\を使用します。文字列エスケープと正規表現エスケープがそれぞれ必要です。Pythonではraw string(r'\\')を使えば\\だけで十分です。
Q10. 正規表現でHTMLをパースすべきでない理由は?
HTMLは正規言語ではなく文脈自由言語です。ネストされたタグ、コメント、CDATAセクションなどを正規表現で正しく処理できません。HTMLパーサー(BeautifulSoup、Cheerioなど)を使用すべきです。
13. クイズ
Q1. パターン \d{3}-\d{4} は"010-1234-5678"で何をマッチしますか?
最初の「010-1234」をマッチします。re.findallを使えば「010-1234」と「234-5678」の両方を見つけられます。電話番号全体をマッチするには\d{3}-\d{4}-\d{4}パターンを使用する必要があります。
Q2. なぜ (a+)+b は危険なパターンですか?
入力がbで終わらない場合(例:「aaaaac」)、エンジンが内部グループと外部グループのすべての組み合わせを試行し、指数的なバックトラッキングが発生します。aが25個でも数千万回の試行が必要になります。単純にa+bに置き換えれば解決します。
Q3. (?:...)と(?=...)の違いは?
(?:...)は非キャプチャグループで、文字を消費しながらグループ化します(キャプチャだけしない)。(?=...)は先読みで、文字を一切消費せず条件のみ確認するゼロ幅アサーションです。
Q4. Go言語でLookaheadを使用できない理由は?
Goのregexpパッケージは RE2エンジンを使用しており、RE2は線形時間保証のためにバックトラッキングを使用しません。Lookaheadはバックトラッキングが必要な機能であるため、RE2ではサポートされていません。
Q5. 以下のうち「abc」にマッチするパターンは? a) [abc]+ b) [^abc]+ c) a.c d) a\bc
a) [abc]+ — 「abc」全体にマッチします。b) [^abc]+ — a, b, c以外の文字のみマッチするため失敗。c) a.c — .がbをマッチするため「abc」にマッチ。d) a\bc — \bは単語境界ですが、aとbの間に単語境界はないため失敗。正解はaとcです。
14. 参考資料
- Mastering Regular Expressions (Jeffrey Friedl) — 正規表現のバイブル
- Regular-Expressions.info — https://www.regular-expressions.info/
- regex101 — https://regex101.com/ — オンライン正規表現テスター
- Regexr — https://regexr.com/ — 視覚的な正規表現学習
- MDN Web Docs: Regular Expressions — JavaScript正規表現リファレンス
- Python re module docs — https://docs.python.org/3/library/re.html
- RE2 Syntax — https://github.com/google/re2/wiki/Syntax
- ReDoS Prevention — OWASPガイド
- ripgrep (rg) — https://github.com/BurntSushi/ripgrep
- Debuggex — https://www.debuggex.com/ — 正規表現の可視化
- RegExp Playground — https://regexper.com/ — レイルロードダイアグラム
- オートマトン理論 — 正規言語と有限オートマトン理論