はじめに
正規表現によるパターンマッチと文字列置換だけで実装しようとするとどうしても複雑になってしまいがちな処理を、マッチした文字列を別途実装した関数に処理させて、その処理結果で置換してしまうとすっきりするよ、というお話。
ついでなので、どうしても実装がうろ覚えになりがちな時刻処理を題材としました。
処理内容は「UNIX時刻(UNIX Epoch)をYYYY.mm.dd HH:MM:SS形式に変換」で、サンプルは、1560663000 [info] 0.0.0.0:8080 server started.
というログ的な文字列を人が見てわかるようにするプログラムです。
zaki@mascarpone% date -d @1560663000 "+%Y.%m.%d %H:%M:%S"
2019.06.16 14:30:00
Perl
置換先に指定する内容(s/pattern/__この部分__/
)は通常は文字列リテラルだが、正規表現のオプションにe
を付加すればPerlコードとして解釈される。
#!/usr/bin/perl
$logstr = "1560663000 [info] 0.0.0.0:8080 server started.";
$logstr =~ s/^(\d+)/epoch2ymdhms($1)/e;
print $logstr, "\n";
sub epoch2ymdhms {
my @time = (localtime shift)[5,4,3,2,1,0];
$time[0] += 1900;
$time[1] += 1;
return sprintf("%04d.%02d.%02d %02d:%02d:%02d", @time);
}
実行結果
zaki@mascarpone% ./sample.pl
2019.06.16 14:30:00 [info] 0.0.0.0:8080 server started.
コードを直接書けるので関数呼び出しでなくパターン置換構文内にワンライナーで書くこともできる。
また、使い捨てフィルタのように書式指定の必要がなく単にlocaltime()
に突っ込んだ結果でもよければ、以下のように雑に実装しても良い。
$logstr = "1560663000 [info] 0.0.0.0:8080 server started.";
$logstr =~ s/^(\d+)/localtime $1/e;
結果
zaki@mascarpone% ./sample.pl
Sun Jun 16 14:30:00 2019 [info] 0.0.0.0:8080 server started.
PowerShell
PowerShell標準の-replace
演算子ではコードとして評価することはできないが、C#のregex
を使用することで、コードを実行することができる。(スクリプトブロックを指定可能)
また、PowerShellではUNIX Epoch基準の時刻処理は対応していないため、基準となる日時(1970年1月1日)をハードコーディングしてやる必要がある…模様。
function Get-Epoch2Date {
param($epoch)
Get-Date([datetime]"1970/1/1 0:0:0 GMT").AddSeconds($epoch) -Format "yyyy.MM.dd HH:mm:ss"
}
$logstr = "1560663000 [info] 0.0.0.0:8080 server started."
$logstr = [Regex]::Replace($logstr, "^(\d+)", { Get-Epoch2Date $args.value })
echo $logstr
実行結果
PS C:\Users\zaki\src\regexp> .\sample.ps1
2019.06.16 14:30:00 [info] 0.0.0.0:8080 server started.
PowerShell(C#)ではスクリプトブロックを指定できるので、これもワンライナーで書ける。前述のPerlの簡潔な例と同様、書式指定が不要であれば以下のようにも書ける。
$logstr = "1560663000 [info] 0.0.0.0:8080 server started."
$logstr = [Regex]::Replace($logstr, "^(\d+)", { ([datetime]"1970/1/1 0:0:0 GMT").AddSeconds($args.value)})
結果
PS C:\Users\zaki\src\regexp> .\powershell.ps1
06/16/2019 14:30:00 [info] 0.0.0.0:8080 server started.
なんでmonth/day/year表記なんだろ。どっかにロケール情報とかあるのかなぁ…
Python
Pythonは、前述Perl/PowerShellと少し異なり、マッチオブジェクト一つのみを引数にとる関数名のみ指定(C的に言うと関数ポインタを指定する感じかな?)することができる。
そのためPerl/PowerShellより自由度はちょっと低く、インラインでコードを記述することはできない。
(9/6 コメント欄より) lambda式を使ってインラインで記述することもできる (TODO:あとでコードも書く)
#!/usr/bin/python
import re
from datetime import datetime
def epoch2ymdhms(m):
return datetime.fromtimestamp(float(m.group(0))).strftime("%Y.%m.%d %H:%M:%S")
if __name__ == '__main__':
logstr = "1560663000 [info] 0.0.0.0:8080 server started."
logstr = re.sub('^(\d+)', epoch2ymdhms, logstr)
print(logstr)
実行結果 (Python 2.7.16でも同様)
zaki@mascarpone% ./sample.py
2019.06.16 14:30:00 [info] 0.0.0.0:8080 server started.
インラインでは書けないが、 書式指定を省略するとこんな感じ
def epoch2ymdhms(m):
return str(datetime.fromtimestamp(float(m.group(0))))
結果
zaki@mascarpone% ./sample.py
2019-06-16 14:30:00 [info] 0.0.0.0:8080 server started.
型変換多いな…