Pythonで、掲題通り、改行を含んだ文字列を検索とか置換を行いたくて、その正規表現について調べました。
対象文字列の例
※日本語の文字列です
以下の文字列が、test_textに代入されているケースを想定
あああ
いいい
[start:ううう
えええ
おおお
かかか:end]
ききき
くくく
[start:けけけ
こここ:end]
さささ
[start:~~~:end]
の文字列を抽出、置換したかった。
問題点分解
- 正規表現による改行をまたいだ文字列の検索
- 検索結果が複数あった場合の正確な抽出
結論
以下の正規表現でいけます。
import re
~~~省略~~~
replace_word = "\[start:.*?:end\]"
# 抽出
word_list = re.findall(replace_word, test_text, flags=re.DOTALL)
# 置換
new_text = re.sub(replace_word, "new_word\n", test_text, count=1, flags=re.DOTALL)
word_listは以下の通り
word_list[0] = "[start:ううう\nえええ\nおおお\nかかか:end]"
word_list[1] = "[start:けけけ\nこここ:end]"
new_textは以下の通り
new_text = あああ\nいいい\nnew_word\nききき\nくくく\nnew_word\nさささ
肝となるところ
3つあります。
re.findall、re.subの引数
flags=re.DOTALL
'.'
特殊文字を、改行を含むあらゆる文字にマッチさせます。このフラグがなければ、'.'
は、改行 以外の あらゆる文字とマッチします。インラインフラグの(?s)
に相当します。参考:re.DOTALL
正規表現
使った正規表現:.*?
それぞれの説明:
.
(ドット) デフォルトのモードでは改行以外の任意の文字にマッチします。
DOTALL
フラグが指定されていれば改行も含む全ての文字にマッチします。*
直前の正規表現を 0 回以上、できるだけ多く繰り返したものにマッチさせる結果の正規表現にします。例えば
ab*
は'a'
、'ab'
、または'a'
に任意個数の'b'
を続けたものにマッチします。?
直前の正規表現を 0 回か 1 回繰り返したものにマッチさせる結果の正規表現にします。例えば
ab?
は'a'
あるいは'ab'
にマッチします。参考:正規表現のシンタックス
re.subの引数
count=1
オプション引数 count は出現したパターンを置換する最大の回数です。
count
は非負整数です。省略されるか 0 なら、出現した全てが置換されます。パターンへの空マッチは前の空マッチに隣接していないときのみ置換されるので、 sub('x*', '-', 'abxd')
は'-a-b--d-'
を返します。参考:re.sub
うまくいかなかったケース
- 正規表現
.*?
のところを(.|\s)*?
としていた
※正規表現の\s
は余白を示す正規表現。改行コードにもマッチする。
例
matches = re.findall('(\[start:(.|\s)*?:end\])', test_text)
半角文字のみだった場合は、うまくいきましたが、日本語のように2バイト文字が混ざったケースではうまくいきませんでした。
その他、参考
- re — 正規表現操作
-> Python公式 - [Python] 正規表現で、改行を含む文字列をマッチさせる
-> うまくいかなかったケースでしたが、正規表現について公式情報含め詳しく調べるきっかけになりました。 - Python: 正規表現の基本 – 最長、最短マッチング
-> 繰り返しパターンがマッチするケースでどのように実装するかが参考になりました。 - Python: 正規表現で複数行マッチングの置換を行う
-> 複数行マッチングの方法で、reモジュールのflagsオプションの使い方について参考になりました。 - 基本的な正規表現一覧(リンク切れ)
-> 正規表現で、複数の条件を入る場合の記載方法について参考になりました。(公式にも書かれていますが)
例re.findall("条件1"|"条件2", test_text, flags=re.DOTALL)
コメント