正規表現
今回はちょっと脱線して、某所に少し書き込みがありましたのでperlの正規表現のお話です。
perlなんか関係ないだろと思われる方もいらっしゃると思いますが、正規表現はPOSIXに準拠しておりperlもTJSもほぼ一緒です。
これを覚えてると、ライター兼スクリプターとしては検索や置換が劇的に効率良くなります。
前置きはこのくらいにして、今回問題はこれです。
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
html側からformで送られて来たデータをデコードする部分です。
これはcgiを作ろうとするとまず最初に躓くところですね。
私もこれを完全に理解するのに2日かかりました。 理解するとどうと言う事も無い事なのですが。
例えばこんな文字列
これは テストだよ
これをformで受け渡しすると・・・・・
%82%B1%82%EA%82%CD+%83e%83X%83g%82%BE%82%E6
こう言う文字列になります。この法則は以下の通りです。
●ルール1 半角英数文字『*-.@_』はそのまま表記。
●ルール2 半角スペースは『+』に変換される。
●ル^ル3 半角表示のその他の記号は『%+2桁の16進1バイト』で表現される。
●ルール4 全角文字は『%+2桁の16進の2バイト』で表現される。
あと全角カタカナひらがなの一部に、2バイト目にアルファベットを使うと言う特殊なルールがあります。(今回の例では『テスト』)
これに関しては、なぜそうなのかは”2バイト文字”等を検索してください。
またJISの場合にはまた少し違ったルールがあったりしますが、日本の場合SHIFT-JISかUTF-8が多いのであまり気にしなくても良いと思います。
まず$value =~ tr/+/ /;は問題無く理解出来るかと思います。
文字+をまず半角スペースにデコードしています。
問題は次の行かと思いますが、これは動きを順番に追って行くと理解しやすいかと思います。
まずは以下の式を観てください。
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/$1/g;
これを実行すると、
82B182EA82CD 83e83X83g82BE82E6
となります。
これは単純に結果だけを見ると%を取り除いた処理……と見えますが、実はここを理解出来ればほぼ8割理解したも同然です。
この書式は%の文字の後にアルファベットの大文字か小文字のA~F・もしくは数字一桁が2回連続して現れたらその3桁のうち先頭の%を取り除いて、その条件にあてはまらなかったものはそのままで文字列を組み直せと言う命令です。
なので処理されたのは赤い部分です。
%82%B1%82%EA%82%CD %83e%83X%83g%82%BE%82%E6
単純に%を取りのぞくだけなら$value =~ s/%//g;で済みます。
最後のgは、マッチしても(条件を満たした部分を見つけても)文字列の最後まで何度でも繰り返し処理を行えと言う命令です。
$1と言うのは、s/~/に挟まれた部分にマッチした部分の文字列(ただしこの場合は%は()の外にあるので()の中のマッチしたアルファベット・数字の部分だけの文字列)を指し示す変数(代名詞)です。
なのでマッチしなかった部分は$1には代入されません。この表現式では処理外として、そのままただ文字列の並び通りにくっつけられます。(←こことても重要)
eとかXとかgとかがそのまま何事もなかったかのようにくっついてるのが見て取れます。
ここまで理解出来れば、もうほぼ分かったも同然です。
次に第二段階の数式です。
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/hex($1)/eg;
hex()と言うのは、中の数字を10進数にする関数です。
出力された結果は以下のものに変化します。
130177130234130205 131e131X131g130190130230
先頭の%82%B1を見ると130177と変換されてるのがわかると思います。
/eは、変換側を数式と解釈すると言う意味です。 意味が分からなければeを外して見ると一目瞭然です。
最後に、
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
ここまで来れば簡単と思います。
$1の2桁の16進数表記の文字列としてマッチした部分を次々と1バイトコードに書き換えてます。(マッチしなかった部分は何もせずそのままくっつける)
あとはそれをHTMLでSHIFT-JIS形式で読めば、『これは テストだよ』と表示されます。
さてこの二段階目・三段階目で重要なのは、%82%B1....と言う文字列を見つけたら、まず82B1....と言う処理をしてから次に130177....に変換してるのでは無いと言う事です。
%82を見つけたら、%82>82>130>130に対応する1バイトコードに変換して次に%B1を……と1回のマッチ毎にデコード処理をして次に進んでると言う事です。
一見当たり前の事を言ってるようですが、ここを理解してないと初心者はハマります。
「正規表現はパズルを組むようなもの」と言う人もいますがまさにその通りで、時にこれ以外には表現式はあり得ないと言うものが存在します。
この文字のデコード用の式もそうで、これをわかりやすくしようといくつかの式に分解しようとするとする恐ろしくハマります。と言うかハマりました。
だからどのサイトでもこの表現式しか書かれてないのですが、perlにとっては正規表現に関する部分が最も奥が深く難解で、それを最も端的に表す美しい式がこの式で、同時に初心者に最初に鉄壁の壁として立ち塞がるのがこの式だったりします。
私もそれ程正規表現は詳しい方ではありませんが、経験則ではこれを理解したおかげで一気にperlを理解出来たような気がします。
なので、この表現式を目当てに来られた方は、頑張って読み解いてください。
perlなんか関係ないだろと思われる方もいらっしゃると思いますが、正規表現はPOSIXに準拠しておりperlもTJSもほぼ一緒です。
これを覚えてると、ライター兼スクリプターとしては検索や置換が劇的に効率良くなります。
前置きはこのくらいにして、今回問題はこれです。
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
html側からformで送られて来たデータをデコードする部分です。
これはcgiを作ろうとするとまず最初に躓くところですね。
私もこれを完全に理解するのに2日かかりました。 理解するとどうと言う事も無い事なのですが。
例えばこんな文字列
これは テストだよ
これをformで受け渡しすると・・・・・
%82%B1%82%EA%82%CD+%83e%83X%83g%82%BE%82%E6
こう言う文字列になります。この法則は以下の通りです。
●ルール1 半角英数文字『*-.@_』はそのまま表記。
●ルール2 半角スペースは『+』に変換される。
●ル^ル3 半角表示のその他の記号は『%+2桁の16進1バイト』で表現される。
●ルール4 全角文字は『%+2桁の16進の2バイト』で表現される。
あと全角カタカナひらがなの一部に、2バイト目にアルファベットを使うと言う特殊なルールがあります。(今回の例では『テスト』)
これに関しては、なぜそうなのかは”2バイト文字”等を検索してください。
またJISの場合にはまた少し違ったルールがあったりしますが、日本の場合SHIFT-JISかUTF-8が多いのであまり気にしなくても良いと思います。
まず$value =~ tr/+/ /;は問題無く理解出来るかと思います。
文字+をまず半角スペースにデコードしています。
問題は次の行かと思いますが、これは動きを順番に追って行くと理解しやすいかと思います。
まずは以下の式を観てください。
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/$1/g;
これを実行すると、
82B182EA82CD 83e83X83g82BE82E6
となります。
これは単純に結果だけを見ると%を取り除いた処理……と見えますが、実はここを理解出来ればほぼ8割理解したも同然です。
この書式は%の文字の後にアルファベットの大文字か小文字のA~F・もしくは数字一桁が2回連続して現れたらその3桁のうち先頭の%を取り除いて、その条件にあてはまらなかったものはそのままで文字列を組み直せと言う命令です。
なので処理されたのは赤い部分です。
%82%B1%82%EA%82%CD %83e%83X%83g%82%BE%82%E6
単純に%を取りのぞくだけなら$value =~ s/%//g;で済みます。
最後のgは、マッチしても(条件を満たした部分を見つけても)文字列の最後まで何度でも繰り返し処理を行えと言う命令です。
$1と言うのは、s/~/に挟まれた部分にマッチした部分の文字列(ただしこの場合は%は()の外にあるので()の中のマッチしたアルファベット・数字の部分だけの文字列)を指し示す変数(代名詞)です。
なのでマッチしなかった部分は$1には代入されません。この表現式では処理外として、そのままただ文字列の並び通りにくっつけられます。(←こことても重要)
eとかXとかgとかがそのまま何事もなかったかのようにくっついてるのが見て取れます。
ここまで理解出来れば、もうほぼ分かったも同然です。
次に第二段階の数式です。
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/hex($1)/eg;
hex()と言うのは、中の数字を10進数にする関数です。
出力された結果は以下のものに変化します。
130177130234130205 131e131X131g130190130230
先頭の%82%B1を見ると130177と変換されてるのがわかると思います。
/eは、変換側を数式と解釈すると言う意味です。 意味が分からなければeを外して見ると一目瞭然です。
最後に、
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
ここまで来れば簡単と思います。
$1の2桁の16進数表記の文字列としてマッチした部分を次々と1バイトコードに書き換えてます。(マッチしなかった部分は何もせずそのままくっつける)
あとはそれをHTMLでSHIFT-JIS形式で読めば、『これは テストだよ』と表示されます。
さてこの二段階目・三段階目で重要なのは、%82%B1....と言う文字列を見つけたら、まず82B1....と言う処理をしてから次に130177....に変換してるのでは無いと言う事です。
%82を見つけたら、%82>82>130>130に対応する1バイトコードに変換して次に%B1を……と1回のマッチ毎にデコード処理をして次に進んでると言う事です。
一見当たり前の事を言ってるようですが、ここを理解してないと初心者はハマります。
「正規表現はパズルを組むようなもの」と言う人もいますがまさにその通りで、時にこれ以外には表現式はあり得ないと言うものが存在します。
この文字のデコード用の式もそうで、これをわかりやすくしようといくつかの式に分解しようとするとする恐ろしくハマります。と言うかハマりました。
だからどのサイトでもこの表現式しか書かれてないのですが、perlにとっては正規表現に関する部分が最も奥が深く難解で、それを最も端的に表す美しい式がこの式で、同時に初心者に最初に鉄壁の壁として立ち塞がるのがこの式だったりします。
私もそれ程正規表現は詳しい方ではありませんが、経験則ではこれを理解したおかげで一気にperlを理解出来たような気がします。
なので、この表現式を目当てに来られた方は、頑張って読み解いてください。