正規表現の基礎まとめ
正規表現の基礎をまとめた備忘録
正規表現とは
正規表現(Regular Expression)は、文字列のパターンマッチングに使われる表現方法の一つです。
文字列に含まれる文字や文字列のパターンを指定して、一致する文字列を検索したり、置換したりすることができます。
正規表現習得のロードマップ
- 正規表現の基本的な概念や表現方法の習得
- 正規表現を使用したプログラムコードのパターンマッチング、データの抽出、ログの解析の習得
- 正規表現の後方参照、肯定先読み、否定後読みの習得
1. 正規表現の基本的な概念や表現方法の習得
メタ文字
正規表現において、メタ文字(特殊文字)は、パターンマッチングを行うために使用される特別な文字です。
以下に一般的なメタ文字を示します。
-
.(ドット)
:任意の1文字にマッチします。 -
*
:直前の文字やグループが0回以上繰り返されることを表します。 -
+
:直前の文字やグループが1回以上繰り返されることを表します。 -
?
:直前の文字やグループが0回または1回現れることを表します。 -
[]
:角括弧内に含まれる任意の1文字にマッチします。ハイフン(-)を使用して範囲を指定することもできます。- 例えば、
[abc]
は、a、b、またはcのいずれか1文字にマッチします。 - 例えば、
[a-z]
は、aからzまでのすべての小文字にマッチします。
- 例えば、
-
^
:行の先頭にマッチします。例えば、^abc
は、行の先頭にabcが現れる場合にマッチします。 -
$
:行の末尾にマッチします。例えば、abc$
は、行の末尾にabcが現れる場合にマッチします。 -
\
:エスケープ文字として使用されます。例えば、.
はドット文字にマッチします。
文字クラス
正規表現の文字クラスは、正規表現でマッチする文字の集合を表現するための方法です。
文字クラスは、 角括弧([])
で囲まれた文字の集合で表されます。
文字クラスを使用することで、指定した文字集合内の任意の1文字にマッチすることができます。
例えば、 [abc]
という文字クラスは、a、b、cのいずれかの1文字にマッチすることを意味します。
また、複数の文字集合を組み合わせることもできます。
例えば、 [a-z&&[^aeiou]]
という文字クラスは、aからzまでのアルファベットのうち、母音を除く1文字にマッチすることを意味します。
クォンティファイア(quantifier)
正規表現のクォンティファイアは、正規表現のパターンに対して、繰り返しの回数を指定するための構文です。
クォンティファイアを使用することで、正規表現パターン内で繰り返される文字、文字列、またはサブパターンの回数を指定できます。
以下にクォンティファイアを示します。
-
*
: 直前のパターンを0回以上繰り返す。例えば、ab*cは、"ac", "abc", "abbc"などにマッチします。 -
+
: 直前のパターンを1回以上繰り返す。例えば、ab+cは、"abc", "abbc"などにマッチしますが、"ac"にはマッチしません。 -
?
: 直前のパターンを0回または1回繰り返す。例えば、ab?cは、"ac"または"abc"にマッチします。 -
{n}
: 直前のパターンをn回繰り返す。例えば、a{3}は、"aaa"にマッチします。 -
{n,}
: 直前のパターンをn回以上繰り返す。例えば、a{2,}は、"aa", "aaa", "aaaa", ...にマッチします。 -
{n,m}
: 直前のパターンをn回以上m回以下繰り返す。例えば、a{2,4}は、"aa", "aaa", "aaaa"にマッチします。
アンカー
正規表現のアンカーとは、文字列の特定の位置を指定するために使用される特殊な文字です。
アンカーは、パターンの先頭、末尾、または単語の境界を示すことができます。
以下に一般的なアンカーを示します。
-
^
: 行の先頭 -
$
: 行の末尾 -
\A
: 文字列の先頭(マルチラインモードでは、各行の先頭でも使用されます) -
\Z
: 文字列の末尾または行の末尾(マルチラインモードでは、各行の末尾でも使用されます) -
\b
: 単語の境界 -
\B
: 単語の境界以外
エスケープ
正規表現のエスケープとは、正規表現内で特殊文字をリテラルとして解釈するために使用される特殊な文字です。
通常、正規表現内で使用される特殊文字は、正規表現の構文において特別な意味を持ちますが、これらの文字をリテラルとして解釈する必要がある場合は、エスケープする必要があります。
エスケープに使用される特殊な文字は、通常、 バックスラッシュ (\)
です。
グループ化
正規表現でグループ化を行うことによって、複数の文字列をまとめて扱ったり、マッチした部分を後で参照したりすることができます。
グループ化は、 括弧 ()
を使って表現します。
例えば、以下の正規表現は、"apple"、"banana"、"orange" のいずれかの単語にマッチします。
(apple|banana|orange)
括弧で囲まれた部分がグループ化され、 バーティカルバー (|)
で区切られた各単語がマッチするようになっています。
また、グループ化された部分は、後で参照することができます。
例えば、以下の正規表現は、"catcat"、"dogdog"、"birdbird" のように、同じ単語が続いている部分にマッチします。
(\w+)\1
\w+
は、1つ以上の単語文字にマッチする正規表現です。
\1
は、1番目のグループと同じ文字列にマッチすることを示しています。
つまり、2つ続いて同じ単語が出現している箇所にマッチします。
キャプチャグループ
正規表現において、グループ化された部分をキャプチャグループと呼びます。
キャプチャグループを使うことで、マッチした文字列の中から、グループ化された部分だけを取り出すことができます。
キャプチャグループは、グループ化と同様に、括弧 ()
を使って表現します。
ただし、グループ化とは異なり、キャプチャグループの中の文字列は、後で参照することができます。
キャプチャグループを参照するためには、\1
、\2
、\3
などのバックリファレンスを使います。
これらのバックリファレンスは、キャプチャグループがマッチした部分と同じ文字列にマッチします。
例えば、以下の正規表現は、"John Doe" という名前の文字列から、"John" と "Doe" という2つの単語を取り出します。
(\w+)\s+(\w+)
\w+
は、1つ以上の単語文字にマッチする正規表現です。
\s+
は、1つ以上の空白文字にマッチします。
この正規表現は、1番目のキャプチャグループに "John"、2番目のキャプチャグループに "Doe" を設定します。
例えば、Pythonの reモジュール
を使ってこの正規表現を実行すると、以下のようになります。
import re
pattern = r'(\w+)\s+(\w+)'
string = 'John Doe'
match = re.match(pattern, string)
print(match.group(1)) # "John"
print(match.group(2)) # "Doe"
match.group(1) は、1番目のキャプチャグループにマッチした文字列を取得します。
同様に、match.group(2) は、2番目のキャプチャグループにマッチした文字列を取得します。
特殊シーケンス
正規表現には、特殊シーケンスと呼ばれる便利な機能があります。
特殊シーケンスを使うことで、一般的なパターンに対して簡単にマッチングを行うことができます。
以下に一般的な特殊シーケンスを示します。
-
\d
:1つの数字にマッチします。 -
\D
:数字以外の1つの文字にマッチします。 -
\w
:単語文字 (アルファベット、数字、アンダースコア) のいずれかにマッチします。 -
\W
:単語文字以外の1つの文字にマッチします。 -
\s
:空白文字 (スペース、タブ、改行など) のいずれかにマッチします。 -
\S
:空白文字以外の1つの文字にマッチします。 -
\b
:単語の境界にマッチします。 -
\B
:単語の境界以外の位置にマッチします。
これらの特殊シーケンスは、文字クラスや量指定子と一緒に使われることが多いです。
例えば、以下の正規表現は、1つの数字が3回続いた文字列にマッチします。
\d{3}
量指定子として {3}
を使って、直前の要素である \d
が3回続いたパターンを表しています。
また、 \b
特殊シーケンスは、単語の境界をチェックすることができます。
例えば、以下の正規表現は、"car" という単語にマッチしますが、"scar" にはマッチしません。
\bcar\b
この正規表現では、\b
を単語の境界に置くことで、"car" が独立した単語であることをチェックしています。
2. 正規表現を使用したパターンマッチング・置換、データの分割・抽出、ログ解析の習得
パターンマッチング・置換
正規表現を使用すると、指定したパターンに一致する文字列を検索することができます。
また、検索結果に対して置換を行うこともできます。
Pythonでは、reモジュール
を使用して正規表現を扱うことができます。
文字列の検索
以下のような文字列があったとします。
I have 3 apples and 5 bananas.
この文字列から数字を抽出してみましょう。
以下の正規表現を使用することで、数字に一致する部分文字列を検索することができます。
import re
text = "I have 3 apples and 5 bananas."
pattern = r'\d+'
result = re.findall(pattern, text)
print(result)
出力結果は以下のようになります。
['3', '5']
文字列の置換
以下のような文字列があったとします。
I have 3 apples and 5 bananas.
この文字列から、数字をすべて2倍にして置換してみましょう。
以下のように置換を行うことができます。
import re
text = "I have 3 apples and 5 bananas."
pattern = r'\d+'
def double(match):
return str(int(match.group(0)) * 2)
result = re.sub(pattern, double, text)
print(result)
出力結果は以下のようになります。
I have 6 apples and 10 bananas.
上記の例では、 re.sub()
関数を使用して置換を行っています。
この関数は、第1引数に正規表現のパターン、第2引数に置換用の関数または文字列、第3引数に検索対象の文字列を指定します。
置換用の関数は、マッチしたオブジェクトを引数に受け取り、置換後の文字列を返します。
データの分割・抽出
正規表現を使用すると、文字列を指定したパターンで分割したり、特定のパターンに一致する部分文字列を抽出することができます。
文字列の分割
以下のような文字列があったとします。
apple,banana,orange
この文字列を ,
で分割してみましょう。
以下の正規表現を使用することで、 ,
で区切られた部分文字列を分割することができます。
import re
text = "apple,banana,orange"
pattern = r','
result = re.split(pattern, text)
print(result)
出力結果は以下のようになります。
['apple', 'banana', 'orange']
文字列の抽出
以下のような文字列があったとします。
Name: John Smith, Age: 30, Occupation: Engineer
この文字列から、 Name
と Age
を抽出してみましょう。
以下の正規表現を使用することで、 Name
と Age
に一致する部分文字列を抽出することができます。
import re
text = "Name: John Smith, Age: 30, Occupation: Engineer"
pattern = r'Name: (\w+).*Age: (\d+)'
result = re.search(pattern, text)
print(result.group(1))
print(result.group(2))
出力結果は以下のようになります。
John
30
上記の例では、 re.search()
関数を使用して正規表現に一致する最初の部分文字列を検索し、 group()
メソッドを使用して、マッチした部分文字列を取得しています。
group(1)
は (\w+)
に一致する部分文字列を取得し、 group(2)
は (\d+)
に一致する部分文字列を取得しています。
ログ解析
正規表現は、ログファイルなどのテキストデータの解析にも有用です。
Apacheアクセスログの解析
以下のようなApacheアクセスログがあったとします。
127.0.0.1 - - [01/Apr/2022:10:12:22 +0900] "GET /index.html HTTP/1.1" 200 2326
127.0.0.1 - - [01/Apr/2022:10:12:23 +0900] "GET /styles.css HTTP/1.1" 200 325
127.0.0.1 - - [01/Apr/2022:10:12:24 +0900] "GET /script.js HTTP/1.1" 200 912
このログから、アクセスしたIPアドレス、日付、リクエストされたファイルのパス、ステータスコード、レスポンスサイズを抽出することができます。
以下の正規表現を使用することで、これらの情報を抽出することができます。
import re
log = '127.0.0.1 - - [01/Apr/2022:10:12:22 +0900] "GET /index.html HTTP/1.1" 200 2326'
pattern = r'^([\d\.]+) .* \[(\d{2}/\w+/\d{4}):(\d{2}:\d{2}:\d{2}) .*\] "(\w+) (.+) HTTP/\d\.\d" (\d+) (\d+)$'
result = re.search(pattern, log)
print(result.group(1))
print(result.group(2))
print(result.group(3))
print(result.group(4))
print(result.group(5))
print(result.group(6))
print(result.group(7))
出力結果は以下のようになります。
127.0.0.1
01/Apr/2022
10:12:22
GET
/index.html
200
2326
上記の例では、正規表現によって、以下のようにログから情報を抽出しています。
- IPアドレス:
([\d\.]+)
- 日付:
(\d{2}/\w+/\d{4})
- 時刻:
(\d{2}:\d{2}:\d{2})
- リクエストメソッド:
(\w+)
- リクエストされたファイルのパス:
(.+)
- ステータスコード:
(\d+)
- レスポンスサイズ:
(\d+)
ログのパースと集計
正規表現を使用して、ログファイルから情報を抽出し、集計することもできます。
以下は、CSV形式で出力されたログファイルを読み込み、各行のstatusカラムの値を集計する例です。
import csv
import re
filename = 'access_log.csv'
with open(filename, 'r') as f:
reader = csv.DictReader(f)
count = {}
for row in reader:
status = row['status']
if re.match(r'\d{3}', status):
if status in count:
count[status] += 1
else:
count[status] = 1
print(count)
出力結果は以下のようになります。
{'200': 100, '404': 10, '500': 3}
記の例では、正規表現を使用して、statusカラムの値が3桁の数字である行のみを集計しています。
正規表現 \d{3}
は、3桁の数字にマッチします。
count辞書には、各ステータスコードの出現回数が格納されます。
3. 正規表現の後方参照、肯定先読み、否定後読みの習得
後方参照
後方参照は、正規表現内でキャプチャしたグループを後方から参照するための機能です。
具体的には、 \n(nは数字)
というエスケープシーケンスを使用します。
\n
は、n番目にキャプチャしたグループにマッチします。
例えば、 (\d{4})-(\d{2})-(\d{2})
という正規表現を使用して、日付を表す文字列(例:2022-01-01)から年、月、日をキャプチャした場合、後方参照を使用して同じ日付を表す文字列を検索する場合には、 \1-\2-\3
という正規表現を使用します。
\1
は年を表し、 \2
は月を表し、 \3
は日を表します。
肯定先読み
肯定先読みは、マッチした文字列の次に特定の文字列が出現する場合にのみマッチする機能です。
具体的には、 (?=pattern)
という構文を使用します。
例えば、 (?=\d{3}-\d{2}-\d{4})\d{1,2}/\d{1,2}/\d{2}
という正規表現を使用すると、日付を表す文字列(例:01/01/22)のうち、パターンxxx-xx-xxxx(例:123-45-6789)が続く場合にのみマッチします。
否定後読み
否定後読みは、マッチした文字列の前に特定の文字列が出現しない場合にのみマッチする機能です。
具体的には、 (?<!pattern)
という構文を使用します。
例えば、 (?<!\d)\d{3}-\d{2}-\d{4}
という正規表現を使用すると、数字が出現する文字列のうち、ハイフンで区切られたパターンxxx-xx-xxxxが出現する場合にのみマッチします。
Discussion