X(Twitter) Zenn GitHub RSS 共有

すばらしき正規表現の世界

作成日時:2023-10-19
更新日時:2024-08-01

例えば

あなたはリーダーに100万行あるサーバーログを渡されてこう言われました。
「ユーザAが行った操作Bの内、条件がC/D/Eに当てはまるログを抜き出して」
どうする?1行ずつ見ていく?

そして作業を始めて3時間経過した後にリーダーは言いました。
「ごめん、ユーザAじゃなくてユーザBだったわ。」

3時間の作業は無に帰し、残業が確定しました。

正規表現を使えば1コマンドで終わるというのに。

正規表現のススメ

例は大げさすぎるが、正規表現を使えるようになれば日々の業務は楽になると思う。
何でも出来るぞ正規表現。

「aから始まってbで終わる行を検索したい」
「数値だけが記載されている行を検索したい」
「入力された値が郵便番号(000-0000)の形式に適合しているか確認したい」
「CSVの1列目と4列目を交換したい」
「数値にカンマ入れたい」
「小文字を大文字にしたい」
etc…

「例えば」の場合は、「ユーザAが行った操作Bの内、条件がC/D/Eに当てはまるログを抜き出す」正規表現を書けば良かっただけである。
「ユーザB」に変更するとしてもすぐ修正し、すぐ調査を実行できる。

主に

とかで便利。

正規表現の基礎

書くのが面倒なのでリンク貼る。

とほほの正規表現入門 - とほほのWWW入門

私も全部は覚えてないけども、
少なくとも上記サイトの任意文字からエスケープシーケンスの章を覚えれば仕事は楽になるかもしれない。

余裕があれば「先後読み」も。

正規表現の先読み・後読み

先後読みは位置へのマッチ

肯定的先読み 123(?=abc)
否定的先読み 123(?!abc)
肯定的後読み (?<=abc)123
否定的後読み (?<!abc)123

DoS攻撃にも使えるよ

使えるよ!というか、ユーザが入力した正規表現をそのまま使用すると下手すればマシンの計算リソースが奪われる。
そういうサービスを作るなら対策が必要。

ReDoSから学ぶ,正規表現の脆弱性について

使うケースとか

日常業務において、調査とかデータ加工する際に正規表現を使わない日は無い。
以下は例。

Q. プロジェクトディレクトリから、“validation”から始まって”error”で終わるエラーログを出している箇所を検索したいよー!
A. 下記の正規表現でプロジェクトディレクトリ内を検索

validation.*error

Q. スネークケースをキャメルケースに変換したい
A. _を消して、_直後は大文字に置換。

(_|^)(.) → \u$2 に置換

Q. ログから「DEBUG」という文字列が存在しない行を全部消したい
A. 下記の正規表現でマッチする行をブランク等に置換する。

^((?!DEBUG).)*\n

Q. 入力されたパスワードが半角英数字混在で10文字以上である事をチェックしたい
A. はい

^(?=.*[0-9])(?=.*[a-zA-Z])[0-9a-zA-Z]{10,}$

Q. 下記のように100個のメンバを持っているクラスがあって、そのインスタンスが2個ある。
片方のインスタンスの内容を別のインスタンスに各メンバのsetter/getterを使って値を写したいんだけど1個ずつ書くのめんどくさいよー!
to.setXXX(from.getXXX()); ←これ100個も書きたくない

class Sample {
    private String col00;
    private String col01;
    private String col02;
    ...
    private String col99;
    // setter / getterは省略
}

A. 正規表現で作業するなら下記。
※ライブラリとかリフレクションやエクセルのオートフィル使えとかそもそもそんなクソクラス作るなとかの指摘はこの記事では無視する。
※Dtoを何かしら別のインスタンスに詰め替える時とかこの手法を使うかもしれん。

# 1. まず上記のクラスファイルをどこかにコピペした後、private Stringを含まない行を消す。
^((?!private String).)*\n →ブランクに置換

# 2. メンバ名を抜き出す。
private String (.*); → $1 に置換

# 3. この時点でメンバ名のみが残っているので、それを利用して```to.setXXX(from.getXXX());```という形に置換する。
^(.)(.*)$ → to.set\u$1$2(from.get\u$1$2());

# 4. to.setColXX(from.getColXX); が100個出来たので本来のソースにコピペ。

その他

記事に関係ないけど「例えば」における最適解は何か。
汎用的なログ解析ツールを作成し、チーム内に展開することである。
面倒な作業を振られる回数は減るし、作業が楽になる。
出向先での評価も上がるかもしれん。
(ツールのメンテをさせられるかもしれんが)