プロが教える店舗&オフィスのセキュリティ対策術

 「パスワード暗号化について(CGIスクリプト)」の続きとなっています。文字数が800文字を超えてしまったため、分割させていただきました。
 続いて解読処理です。こっちは更に謎です。

$salt = $logpassword =~ /^\$1\$(.*)\$/ && $1 || substr($logpassword, 0, 2);

 最初は//内の処理です。$1$だけは読めますが、「^」も「(.*)」も最後の$も不明です。「^」はEXORではありませんよね・・・?
 次に&&とやはり||です。この辺は「え?ギャグ?」って感じです(まったく分かってません)。
 最後にパターン結合演算子ですが・・・。「スカラー式をm//、s///、tr///と結びつける」と言われても何のことやら。大体上の表記ではmもsもtrも使ってないんで・・・。マッチmの略形でしょうか?

 ということで長くなってしまいましたが、これらの動作の目的、そして動作原理を教えていただきたく質問させていただきました。
 どうかよろしくお願いします。

A 回答 (3件)

おっしゃるとおり//はm//の省略形です。

ですから「^」は、正規表現の
「文字列先頭」をあらわす記号です。よってこの部分は
「$logpasswordが '$1$' で始まり0以上の何文字かが続き$が来るような文字
列か?」
といったことを調査しています。
&&や||は、論理演算子ですね。A && Bで
「Aを評価(実行するってことです)して、真なら続けてBを評価してその結果を
返す。Aが偽ならBには一切感知せず偽を返す」
ということになり、A || Bで
「Aを評価して、真ならBには一切感知せずその結果を返す。Aが偽なら引き続
きBを評価してその結果を返す。」
ということになります。

上記の意味は総じて以下のようなことなんじゃないでしょうか。

「$logpasswordがこれこれのような文字列で、しかも途中の(.*)部分が
真(つまり空文字列じゃない)だったときはその(.*)部分を、
そうじゃないときは$logpasswordの先頭から2文字を取り出して、
saltに入れてください」

if文で書きなおすと上記の式は以下のようなことに
なると思います。
if ($logpassword =~ m/^\$1\$(.*)\$/) {
if (defined($1) && $1 ne '') {
$salt = $1;
} else {
$salt = substr($logpassword, 0, 2);
}
} else {
$salt = substr($logpassword, 0, 2);
}

まあたしかに初めてご覧になった方にはわかりやすいものではないでしょうが
…あんまりPerlをいじめないであげてください。&&や||をこのように使うのは
Cでもshellでもいろんな言語にもありまして、UNIX関係者には馴染みがある用法です。
lispなどではむしろifよりもこちらのほうが標準的です。
馴れると最初のような式の方が上記ifを使ったものより読みやすくなったりし
ます。

もちろん、「おれはifで書いた方がいいぜ!」とお思いになるのでしたら
誰にかまうことなくそうお書きになればよろしいでしょう。
どう書いてもいいのがPerlです。

この回答への補足

 早速のご回答ありがとう御座います。とてもわかりやすい説明で助かりました。でももうちょっと分からないところがあるので、迷惑ついでにお願いいたします。

 さて、まず残った疑問点なんですが、やはり、

「$logpassword =~ m/^\$1\$(.*)\$/」

の式ですね。
 ひとまずm//がマッチ演算子だということが理解できました。そして「^」ですが、これは要するに文字列の先頭から評価していくんだ、ということで、いいんですかね?初心者の目で見ると、変数の中身の評価なんていつでも最初から始まるじゃないかと思うんですが・・・。これはそういうことではなく、「n文字目から評価する場合もあるので、それじゃないということを明示している」ということなのでしょうか?

 1,次に(.*)ですが、これは「パターンの中の()で囲んだ部分」ですよね。「.*」は要するに「任意の1文字の0回以上の繰り返し(0回以上ってことは、全く何もない時でも一応評価する、ということでしょうか、それとも0は真でも空文字は偽、を示すのでしょうか)」を示していて、結局は「マッチ成功時に、最初の1文字($1)を引っこ抜いて$saltに代入する」と読んで正解なのでしょうか?
 ・・・なんか何言ってるのか分からないですね(汗)。

 2,そういうことじゃなく、「$_の最初が$1$で始まって途中がどーでもよくて、最後に$が来るっていう文字列だった場合にはその途中の部分を抜き出して$1に代入するんだ」ってことなんですかね。

 ・・・意見が上の2つに分かれています。僕の中で(汗)。
 できれば2番が正解であって欲しいんですが・・・(1番は謎に包まれています)。
 2番が正解なら、「if (defined($1) && $1 ne '') 」の部分も「$1がひとまずなんらかの文字なら」の一言で決着が付くんですよね。

 という感じで考察してみました。いかがなものでしょうか?

 ちなみに、個人的にPerlは好きです。Cをちょっとかじっていたので、Perlの謎の破壊力にちょっと驚いているところです(@log = <IN>;とかforeach(0 .. $#data){}とか)。
 今回、「if($data){}ってなんなんだ!?」ってことを理解した(真と偽について初めて知った)ときのような感じを論理演算子でまた味わいました。慣れるとついつい使ってしまうような表現ですね。面白いです。
 というわけで、また何かしら多分きっとお世話になると思うので今後ともよろしくお願いします。

補足日時:2001/05/11 16:07
    • good
    • 0

こちらこそよろしくお願いします。


正規表現に付いては、それこそ一冊の本にもなっているくらいなので、あとで
じっくりラクダ本やその「詳説 正規表現」(http://www.oreilly.co.jp/BOOK/regex/)
をご覧いただくといいと思いますが、とりあえず簡単に補足しておきます。

まず「^」ですが、これは「頭から評価していく」という意味ではありません。
「n文字目から…」の方が正解です。

$a = 'Perl is not a Pearl';
$b = 'What is Perl?';
のとき、
$a = /^Perl/; は先頭がPerlで始まるので真
$b = /^Perl/; は先頭がWhatで始まるので偽
$a = /Perl/; はPerlを含むので真
$b = /Perl/; もPerlを含むので真
となります。

「.*」は0回でも一致します。/Pe.*rl/ はPerlにもPearlにもマッチします。
1回以上必ずなにかないといけない場合は/Pe.+rl/と「+」を使います。
上の例で言うとこの正規表現は$aでは真、$bでは偽になります。

さて、「()」は実際のカッコを指しているのではないことにご注意ください。
'This is (not) Perl' =~ /(.*)/ も真ですし、'This is not Perl' =~ /(.*)/
も真です。本来の「()」にマッチさせたければ\を使って /\(.*\)/とします。

ただの「()」は「カッコの中のマッチした文字列をこっそり$1に突っ込んでお
いてください」という意味です。$1の1は、カッコの登場順の番号を表します。
例えば
$date = '2001/5/11 21:25:30';
$date =~ m/^(\d+)\/(\d+)\/(\d+) (\d+):(\d+):(\d+)/;
とすると、$1に「2001」が、$2に「5」が、$3に「11」が…$6に「30」が入り
ます。

なお、「\d」は数字を表す記号です。また「/」はそのまま書くと
m//のスラッシュと区別がつかなくなるので「\/」としてあります。これを
避けるためm//を「/」ではない任意の文字で囲む方法もあります。
$date =~ m@^(\d+)/(\d+)/(\d+) (\d+):(\d+):(\d+)@;
これだと@が区切りになるので「/」はそのままでよくなります。このときは
「m」は省略できません(なんか余計な説明加えちゃってかえって混乱させ
ちゃったかな…)。

> (@log = <IN>;とかforeach(0 .. $#data){}とか)

foreachのかわりにmap {} @data; を使うことを覚えたりするとさらにクセに
なりますよ。
    • good
    • 0
この回答へのお礼

 再度お付き合いいただきありがとう御座います。考察2です(笑)。

 なんかやたらと手強い正規表現ですが(最初の頃は謎の羅列でしたが)、片鱗をちょっと垣間見させていただいました。

$logpassword =~ /^\$1\$(.*)\$/

仰る通りに上記を解読してみると・・・。
 まず、基本的に「$1$何か$」という文字列を評価し、「何か」の部分以外がマッチすれば、「.*」であるために「何かの部分が空文字列」でもひとまず真ということですね。
 それで、「/^$1$/」の形であるために「最初が$1$のもの」を評価することになると。
 その後、()があるので()内でマッチするものがあれば$1に代入しろ、と。ここでは()内が「.*」つまり「なんでもいい」ということなんで、結局「$1$」と「$」に挟まれた部分の文字列を$1に代入せよ、と言うことなんですね。なんで$1かっていうのは、()が1つ目だからであると。

 ちょっと意訳しすぎてるところもありますが、おおざっぱに言うとこの正規表現の形はこんな感じでOKですかね?

 ただ、またちょっと疑問というか確認したいことは、「^」はつまり「文字列の0文字目から」という意味になるのでしょうか?だとすると、n文字目の場合はどうなるんでしょうか?リファレンスにも書いてないようなんで・・・。まぁ、これは後学だから別の本を参照すべきですね。

 ¥のエスケープについてはちょっとかじってるのでなんとか理解できました。取得したIPを分けるときなんかにsplit(/\./,$ENV{'りもーとあど'})とするのと一緒ですね。
 dは・・・。フォーマットの時のsprintf("%.3d",$data)みたいなものでしょうか?C言語では、まぁdは整数fは小数点とかありましたけど、あれと一緒ですかね?表記方法も一緒っぽいし。

 んで合ってたら結論ですが、結局この質問って「正規表現なんたら」の問題だったわけですね・・・。CGIとか暗号解読とか、全然関係ないし・・・。正規表現恐るべし、祖国統一万歳ですね。

 それじゃ、これで(この質問は)最後かと思いたいんですが、確認をよろしくお願いします(メールじゃないんでもちろんどなたでも。是非お願いします)。

お礼日時:2001/05/12 03:44

そういう解釈でよろしいと思います。



「n文字目」についてですが、/^.{n}\$1\$/ ですね。{}で繰り返す数を
指定します。でもあまり使うことはないですね。固定で先頭から何文字か
わかってる場合はsubstrで切り出したりできますしね。
substr($xxx, n) =~ /^\$1\$/ ともできます。…でも書いてないのは
リファレンスとしていかがなものか。

dはdecimal, 10進数の略です。sprintfのものも同じ語源だと思います。

Perlの正規表現はさらにものすごく多機能です。ゆっくり覚えていくことを
お薦めします。
    • good
    • 0
この回答へのお礼

 どうもありがとう御座いました。複数回に渡って丁寧に説明して下さり、とてもわかりやすく勉強になりました。
 教えて下さったことをしっかり頭にたたき込んで、これからも勉強していきたいと思います。
 今回は本当に助かりました。心より御礼申し上げます。

悠夜

お礼日時:2001/05/14 09:29

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!