正規表現を未だに使いこなせず、ちょっとしたマッチ処理も度々苦戦するので、ノウハウを蓄積するためにまとめておく。
※表記の誤りや、もっとスマートに表現できる等のアドバイス等ございましたら、ご遠慮なくご指摘頂ければ幸いです。
###1. 最後に出現する指定文字までマッチ
ex) /hoge/fuga/foobar.txt
というパスについて、ファイル名のパスまで取得する。
【考え方】
最初の文字から最後のスラッシュをマッチングさせて抽出させれば良い。
echo /hoge/fuga/foobar.txt | grep -oe ".*\/" # /hoge/fuga/
【解説】
grepの-oオプションはonly matching(一致した部分のみ抽出)の意味。-eは正規表現使用のオプション。
.*
で0文字以上の任意の文字列を表現。最後に\/
(\
はエスケープ文字)でスラッシュまでマッチ。
###2. 最初に出現する指定文字までマッチ
ex) aaa/bbb/ccc/ddd
という文字列について最初の/
(スラッシュ)まで抽出したい。
【考え方】
事例1のパターンでは最長一致でマッチさせているため、最後のスラッシュまで抽出してしまう。
今回のパターンだと、最短一致によって最初に出現したスラッシュまでを抽出したいが、grepで最短一致を使用するにはテクニックが必要。というより対応していないらしいので、-Pオプションの使用によりPerl正規表現を有効にして最短一致を表現する。
echo aaa/bbb/ccc/ddd | grep -oP "^.*?\/" #aaa/
【解説】
^
は先頭文字の意味。これをつけないとaaa/ , bbb/ , ccc/
の3つがマッチしてしまう。?\/
の表現は、?
の直後の文字について、最短で出現した文字をマッチさせる。すなわち、aaa
の直後のスラッシュが最短一致する。
###3. sedによる後方参照(これ便利) ※2018/10/20修正
ログ解析をするときに、特定の値をマッチさせて後処理で演算を行いたいシュチュエーションがある。
例えば下記のようなGCログがあったとする。このログからFullGC直後のメモリ使用量(下記例の1行目で言うと、FullGC: 3328K->384K(3712K)
の384K)の数字384だけを抽出することを考える。
[GC 3.125: [FullGC: 3328K->384K(3712K), 0.0112500 secs] 8168K->5998K(16000K), 0.0113291 secs]
[GC 4.539: [FullGC: 3712K->4K(3712K), 0.0032661 secs] 9326K->5999K(16000K), 0.0033515 secs]
[GC 10.669: [DefNew: 3332K->3K(3712K), 0.0015267 secs] 9327K->5999K(16000K), 0.0015929 secs]
[GC 12.103: [DefNew: 3331K->3K(3712K), 0.0003727 secs] 9327K->5999K(16000K), 0.0004336 secs]
[GC 13.539: [DefNew: 3331K->4K(3712K), 0.0003766 secs] 9327K->5999K(16000K), 0.0004347 secs]
[GC 18.199: [FullGC: 3332K->10K(3712K), 0.0012516 secs] 9327K->6005K(16000K), 0.0013153 secs]
私の従来のやりかただと、例えば抽出したい数値のの前後に記号があるとき(ex: <12345>)や特定のキーワードが数値の前にあるとき(ex: Total:12345)はその特定の記号やキーワードをトリガーにして下記のようにマッチ条件を書いていた。
echo "foo123<12345>123bar" | grep -oe \<[0-9]*\> # <12345>
当然だが、これだと抽出したい数値の前後にトリガー文字が必然的についてきてしまう。
数値だけを抽出したい場合はどうすれば良いのだろうか。
※2018/10/20修正
そこで便利だと感じたのがsedで後方参照を使用する方法である。後方参照とは、マッチした部分文字列を後で再利用することができる機能である。後方参照させたい部分は( ) で囲んで表現し、参照時は \1, \2,,,,とし、何番目の部分文字列を参照したいかを指定することができる。
後方参照の定義の理解に誤りがございました。
パターンにマッチし記憶された部分文字列をパターン内でも参照することが可能 な機能が本来の使われ方となります。
https://www.javadrive.jp/perlregular/ref/index5.html
従って下記の例において、置換前に()で囲み、置換後パターンに後方参照\1,\2,,,として参照して文字列を取り出すということは、本来の後方参照の用途からは逸れるものとなります。
norisuke3@githubさん、ご指摘ありがとうございました。
※修正ここまで
sedは置換機能に優れており、基本的な使い方は次の通り。
sed -r 's/置換前文字/置換後文字/' # -rは拡張正規表現
# ex)
echo hogefuga | sed -r 's/hoge/fuga/' # fugafuga
以上を踏まえると、抽出したい文字列を自由自在に操ることができる。
例えば123<999>456<888>789
という文字列から< >
内の999, 888
だけを抽出する場合は次のように書ける。
echo "123<999>456<888>789" | sed -r 's/^.*<([0-9]*)>.*<([0-9]*)>.*$/\1 \2/' # 999 888
上記の例では抽出したい数値以外の任意文字列は.*
で一括マッチさせ、トリガー文字列(この場合は<
と >
)は明示的に記述している。抽出したい文字列は数値であるから後方参照の括弧で囲い、([0-9]*)
とした。そして、最後に抽出したい数値を後方参照\1 \2
で取り出している。
最後にこれらの基本を押さえると最初のGCログの例ではFullGCの数値は下記のように抽出できる。
cat GClog | grep FullGC | sed -r 's/^.*FullGC:\s[0-9]*K->([0-9]*)K.*$/\1/'
384
4
10
【参考】上記の演算結果で最大値を取得したいときはawkで下記のように書ける。よく使うが忘れるのでメモ。
| awk '{if(max<$1) max=$1} END{print max}'