Python3で文字列中の16進コードを変換
概要
文字列中に'\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88test'のように\xhh形式の16進コードが埋め込まれている場合に、これを元の日本語(「テストtest」)に戻したい。URLに日本語を含むようなページのあるWebサイトだと、Apacheのアクセスログに\xhhのような16進コードが埋め込まれていることがある(*1)ので、これを元の日本語で確認したいためだ。
(*1) http://httpd.apache.org/docs/2.4/ja/mod/mod_log_config.html より。
セキュリティ上の理由により 2.0.46 より、 %r, %i, %o に入っている、 印字不可能な文字と他の特別な文字は、\xhh という形式の文字列でエスケープされるようになりました。hh は そのままのバイトの値の 16 進での値です。この規則の例外には、 バックスラッシュを使ってエスケープされる " と \ と、 C 形式の表記法が使われる空白文字 (\n, \t など) があります。2.0.46 以前のバージョンではエスケープ処理は行われませんので、 生ログファイルを扱う際に注意が必要です。
Perlでは
Perlでは以下のようにできるのを今回はPython3で行いたい。
#!/usr/bin/perl while (my $line = <>) { $line =~ s/\\x([0-9a-fA-F]{2})/pack("H2",$1)/eg; print $line; }
Pythonでは
Python3では以下のように処理できた(\xhhで16進化されている箇所はUTF-8でエンコーディングされているとする)。
#!/usr/bin/python3 import sys import re for line in sys.stdin: line = re.sub(rb'\\x([0-9a-fA-F]{2})', lambda m: bytes([int(m.group(1), 16)]), line.encode('utf-8')).decode('utf-8') print(line, end = '')
基本的にPerlの処理と同じだが、置換処理の前後でstring <-> bytesの変換を行っている点に注意が必要。最初、stringのまま処理しようとしてハマった。
ログの例
127.0.0.1 - - [04/May/2018:13:58:40 +0900] "GET /\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88test HTTP/1.1" 301 264 "-" "curl/7.55.1"
実行例
# python3 conv.py < access_log 127.0.0.1 - - [04/May/2018:13:58:40 +0900] "GET /テストtest HTTP/1.1" 301 264 "-" "curl/7.55.1"
まあ、Perlでやってもよかったのだが。