Edited at

[Perl/PowerShell/Python]正規表現でマッチした文字列を関数で加工した内容に置換する (UNIX Epoch変換)


はじめに

正規表現によるパターンマッチと文字列置換だけで実装しようとするとどうしても複雑になってしまいがちな処理を、マッチした文字列を別途実装した関数に処理させて、その処理結果で置換してしまうとすっきりするよ、というお話。

ついでなので、どうしても実装がうろ覚えになりがちな時刻処理を題材としました。

処理内容は「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コードとして解釈される。


sample.pl

#!/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日)をハードコーディングしてやる必要がある…模様。


sample.ps1

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:あとでコードも書く)


sample.py

#!/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.

型変換多いな…



参考情報