読者です 読者をやめる 読者になる 読者になる

初代Masteries

きっとモヒカンにもなれないお前たちに告げる!!!

BitcasaのカオスなPerlワンライナーを読み解いてみた.

Bitcasaのとあるページに書かかれているPerlのワンライナーがカオス! ...みたいなつぶやきを見つけたので, 自分なりに読み解いてみました.

勘違いしている部分や説明が間違っている部分が多数存在すると思われます.
お気づきの方はtwitter等でご指摘頂ければ嬉しいです. 宜しくお願い致します.

$h="5261776264206775722076617376617667722065726663626166766879642";
$h=~y/48/cf/;$_=pack("H*","${h}e0a");@a=split//,"/-35753=?=357"x2;
s.([a-zA-Z]).ord$1<97?uc($1^$a[ord($1)-65]):lc($1^$a[ord($1)-97]).ge;print

まずはperltidyで綺麗にしてみます.

$h = "5261776264206775722076617376617667722065726663626166766879642";
$h =~ y/48/cf/;
$_ = pack( "H*", "${h}e0a" );
@a = split //, "/-35753=?=357" x 2;
s.([a-zA-Z]).ord$1<97?uc($1^$a[ord($1)-65]):lc($1^$a[ord($1)-97]).ge;
print

...多少読みやすくなりましたね.
というわけで, 1行目から順番に読みといていきましょう.

$h = "5261776264206775722076617376617667722065726663626166766879642";

これはOKですね.
変数$hに, 文字列を格納しているだけです.

$h =~ y/48/cf/;

yはtrと同じく変換演算子を意味します.
この場合, $hの文字列に含まれる'4'を'c'に, '8'を'f'に変換します.
従って, $hはこのように書き換わります.

526177626c206775722076617376617667722065726663626166766f796c2

...個人的には次の1行が難関でした.
理由は簡単, 今までpackとか使ったことなかっただけです...

$_ = pack( "H*", "${h}e0a" );

pack関数は, 第2引数を, 第1引数で与える型の指定に従って変換する...
といっても「どういうこっちゃ?」という感じですね. ひとまず, 具体的に説明していきます.

さて, pack関数は, 第1引数に"H"を与えると,

  1. 第2引数の文字列を16進数と解釈して,
  2. それに該当するASCIIコードの文字列に変換する.

という作業をしてくれます.
本当は, 「型の指定」を行う第1引数には"H"以外にも"C"とかいろいろ入れられるのですが, この辺りも説明しだすと長くなるので今回は省略です...

具体的に, 第1引数に"H"を指定した時のpack関数の挙動を見てみましょう. 多分これであってるはず...

例えば, 'a'という文字のASCIIコードは10進数で97, 16進数では0x61です.
従って, pack("H", "61")は'a'を返します.

更に第1引数を"H*"とした場合, 第2引数の文字列を2文字ずつ, 16進数と解釈してそれに該当するASCIIコードの文字列に変換する... という作業を延々と続けてくれます.
従って, pack("H*", "616263")は'abc'になります.

これによって, $_は次のようになります.

Rawbl gur vasvavgr erfcbafvoyl.\n

...pack関数の詳細については, perldoc.jpのperlpackutあたりを見て頂くのが良さそうですね.
自分も後で熟読しようと思います.

@a = split //, "/-35753=?=357" x 2;

"/-35753=?=357"を2回繰り返したもの, つまり"/-35753=?=357/-35753=?=357"を1文字ずつ区切って, 配列@aに入れているだけですね.

s.([a-zA-Z]).ord$1<97?uc($1^$a[ord($1)-65]):lc($1^$a[ord($1)-97]).ge;

さて, 最後. これ, 置換ですね. もう少しわかり易く書き換えてみると, こうなります.

$_ =~ s/([a-zA-Z])/ord $1 < 97 ? uc($1 ^ $a[ord($1)-65]) : lc($1 ^ $a[ord($1)-97])/ge;

今回調べていて知ったのですが, 置換演算子にeオプションを与えると, 右側の式を評価して, その結果に変換する, という事が出来るんですね!

よってこの行は, 置換演算子の左側, つまり$_に含まれる全てのアルファベットが, 右側の式の評価結果に置換される, ...という訳です.

ord $1 < 97 ? uc($1 ^ $a[ord($1)-65]) : lc($1 ^ $a[ord($1)-97])

この部分ですが, ord関数は, 文字のASCIIコードを求めます. ちなみに, 文字のASCIIコードを文字に変換する(ordの逆)のはchr関数です.
ucは文字列を大文字に, lcは小文字に強制的に変換する関数ですね.
あとはビットごとの排他的論理和(^)を使ってゴニョゴニョしています.

...で, 結果として$_が次のように変換され, 最後のprintで出力される, という感じです.

Enjoy the infinite responsibly.

まとめ

Bitcasaのページに書いてあったワンライナーを, 自分なりに(?)読みといてみました.

お恥ずかしながら, 置換演算子の'e'は知らなかったので思ったより勉強になりました.
pack関数については, ちゃんと説明できるようにperldoc.jpを熟読したいと思います...