初級プログラミングの記事への要望が非常に高い。それに応えずにいるのは、いくつか理由がある。最大の理由は労力に見合わない、ということだ。要望が多いのに、実際に書くと読まれない。あと、初級プログラマが本当に有効な知識を説明しようとしても、やっぱり読まれない。初級プログラミングの話は、非常に予定調和の要求が高くて、他のサイトや本に書いてあることとまるで同じようなことを書かないと読まれない。それは最高に意味がないので、書く意義を見いだせない。それでいてあまり読まれない上にプログラミング記事はプロモーション効果も薄いので非常に書きにくい。今回一応ではあるが、beginnersというカテゴリを作ったので、(記事が読まれれば)今後こうした初級者向け記事は増えていくかもしれない。Yokohamaでは「すべての固有概念は一度は説明される」が、Chienomi的には一般的な用語の説明などはあまりしない。意味を正しく理解していなくても、文脈から読み取ることが可能であるはずだからだ。概要ウェブサービスやログインなど様々な場面で実に多くのパスワードが要求される。し、毎回十分なエントロピーをもったパスワードを作るのは骨が折れる。Linuxではパスワードを生成するプログラムはいくつかあるが、比較的大きなプログラムの一部だったり、エントロピーに不満があったり、なんらかの不満があったので作ってみた。ここで紹介するコードは、一度作ったあと、この記事のために説明的に書き直したものだ。endend解説shebang#!/usr/bin/rubyShebang行である。今回は初回なので、ここも説明しておこう。Unixシステムにおいては「実行可能なテキストファイル」を実行するとき、その一行目を読んでそのテキストファイルを解釈させるプログラムを決定する。これはシステム自体が持っている機能であり、シェルが持っている機能ではないが、シェルもそのような機能を持っている可能性がある。シェルがその機能を持っている場合は、実行した場合に「システムに対してそのプログラムを実行する」という振る舞いではなく、「引数としてそのファイルを渡す」という振る舞いになるから、システムのその機能に頼ることはない。実はperl(1)が同様の機能を持っており、perl(1)はPerlでないshebangを持つスクリプトを起動できる。~/bin/mkpwd.rbという形式で起動されることを意味する。このため、shebang行には引数やオプションを含めることができる。shabang行がない場合、システムはデフォルトのシェルに対する引数としてそのテキストファイルを渡す。これは「実行可能なテキストファイルを実行した場合」の振る舞いであり、このテキストファイルを引数として渡した場合には関係ない。flagを持っている)必要がない。この仕組みであるから起動された処理系はshebang行を含むテキストファイルをソースにすることがある。多くのスクリプト処理系では#はコメントであるから、コメントとして読み飛ばすことができる。中には#がコメントになっていない処理系もある。Lispとか...と言いたいところだが、clispもsbclもちゃんとshebangは読み飛ばすようになっている。理由は、もちろんそうでないと困るから。-*-Shebang行につづいてRubyはマジックコメントを書くことができる。これは、スクリプトエンコーディングを指定することができる。utf-8と書けばスクリプトエンコーディングがUTF-8で書かれていることを明示できる。2.0からはUTF-8がデフォルトだが、明示することでいくつかのトラブルが回避できる可能性が示されている。value`という形式で変数を指定できる。[(公式ドキュメント翻訳版)](https://ayatakesi.github.io/emacs/25.1/Specifying-File-Variables.html){rel="external"}つまり、Emacsの変数として書いておきながらRubyはそれをうまく解釈する、ということである。fileencoding=utf-8:正しく認識する。これは、vimに対してUTF-8のファイルエンコーディングを明示するものだが、これを正しく解釈する。Vim自身Emacs形式で書かれているものもある程度解釈するようである。ここでスクリプトエンコーディングを明示するのはそれほど重要なことではないが、文字の順序が影響するコードなので、ASCII非互換の文字集合だと解釈されると困る。ここでは別にus-asciiでも構わない。1ARGVはコマンドライン引数オブジェクトである。ARGV.shiftで引数オブジェクトの先頭の値を削除し、このメソッドの返り値としてその値を返す。ARGVはArrayインスタンスであり、shiftというメソッドはArrayに属している。Array#shiftは配列の長さが0である(つまりからっぽである)場合、nilを返す。nilはRuby独特の言葉で1、一般的にはnullとかundefinedと呼ばれることが多い。ARGV.shiftは潜在的には(コマンドライン引数を与えられていなければ)nilを返す。後続するメソッドが値が存在することを前提に進めるとエラーになる可能性がある。&.というのはRubyの「ぼっち演算子」である。2.3から登場したものである。このぼっち演算子は後続のメソッドを、レシーバ(この場合ARGV.shiftの結果)がnilでない場合だけ起動する。レシーバがnilだった場合、メソッドは起動されることなくnilを返す。引数があればそれをto_iによって整数化した結果がpwlengthとなるが、引数がなければnilになるので論理ORによって右辺64が代入される。次行では1以下である場合、つまり、引数が0だったり-1だったり、hogehogeだったり(この場合.to_iすると0になる)する場合にデフォルト値にリセットしている。そもそもぼっち演算子が必要な状況ではない。nilが所属するNilClassには#to_iメソッドが存在し、なおかつ0を返すからだ。私の好みとしてはこちらのほうがいい。$1."endのように書くことになるからぼっち演算子が必要になる。今回の場合は「任意性のある2つの引数」をとるため、そのようなことはないはずだ。ARGV.shift&.each_charconverter_symbols変数はパスワードで使用される記号を定義している。Arrayインスタンスである。ここで再びARGV.shiftが登場している。一度shiftしているから2番目の引数だ。結果は、nilかStringインスタンスである。String#each_charはブロックを与えるかどうかで挙動が異なる。ブロックを与えた場合、ブロック変数に対して1文字ずつ(1バイトではなく、エンコーディングで定義された1文字ずつである)代入しながらループする。ブロクを与えなかった場合、Enumeratorインスタンスが返る。Enumeratorクラスはイテレータクラスである。イテレータクラスは「反復可能なコレクション」のクラスである。1.9から組み込みになり、扱いも少し変わった。iendというのは配列に対するイテレーションであり、eachメソッドがイテレータということになるのだが、配列は配列であって繰り返しのためだけに存在しているわけではない。3].eachとすると、enumはEnumratorインスタンスとなり、繰り返しのための存在に変わる。Enumratorクラスにする大きな理由は外部イテレータにすることである。enum.nextのようにして1要素ずつ取り出して処理できる。#...endみたいなことができる。ひとつ、有用な用法が、「反復可能な要素を配列にできる」ことである。これは、Enumerator#to_aという配列化のメソッドがあり、反復される要素を配列にしたものになるからだ。元要素が配列の場合は全く意味がないが、今回の場合文字列であるため、「1文字ずつ分離して配列にする」という簡単かつ速い方法になる。ARGV.shift.split(/(.)/)というやり方もあるが、こっちのほうがわかりにくいだろう。候補文字配列さて、ぼっち演算子を使用しているため、第2引数がなかった場合はeach_charされることなくnilを返す。'~').to_aで三項演算子を使って、nilかどうかによって処理が分かれるようになっている。'/')というのはRangeリテラルだ。Rangeはそのまま、「範囲」を表すものであり、範囲に含まれているかどうかという判定ができたり、イテレータを利用可能であったりする。この範囲はsuccというメソッドを使って作られるようになっており、左の要素をsuccしていって、右の要素になったら終了だ。String#succは文字集合的に「次の文字」を返すようになっている。正確には、アルファベットはアルファベットで「次」を返すし、「数値の文字列」の場合は数値的な「次」を返すが。"ZZ")も正しく動作するので、もうちょっと複雑かつ器用にこなしてくれる。!はASCIIにおける最初の記号、~は最後の記号である。このあたりはシステムと文字エンコーディングに関する知識が必要になる。'~')が「すべてのASCII可視文字」になることがわかるはずだ。この間にはアルファベットや数字が全て含まれている。Range#to_aはrange.each.to_a的な結果を返す。'~').to_aですべてのASCII可視文字からなる配列が得られる。左辺の場合はその方法でアルファベット、数字の文字列を作って指定された記号に連結している。[]).to_aという方法でまとめて1行で書けたりする。配列を集合として扱う方法があるのは比較的珍しい。convloopsこれらは生成時に無駄が起きたり偏りが発生しないように調整するためのものだ。1バイトの最大値は0xffであり、255である。50]で50以上の値は出なくなる(%で商の余りであり、50以上は50で割れるので余りが50以上になることはない)のだが、これだと先頭の6つ(0から数えるから)だけ出る確率が上がる。byteとかやると50から255までの随分大きな範囲がスキップされることになってしまい、「ものすごくサイコロを振り直すランダム」みたいになる。1行目で/によって割っているが、RubyにおいてInterger#/は余りを捨てる。5によって得られる250は正確にループ可能な最大数であり、配列としての最大インデックスは249である(0から始まるから)。byteが最小の「振り直し」ということになる。これを変数化したものである。#...end/dev/urandomはUnixシステムにある疑似乱数生成器である。あり、ちゃんと安全なエントロピーを持っている。/dev/randomは真乱数生成器だが、パスワードに使える長さの乱数を得るのはものすごく大変なのでここでは/dev/urandomで十分。Xといったデスクトップで一般的に使われるUnixシステムには備わっているので問題ないはずだ。なるファイルというものがない。よるランダムな内容が返ってくる。これまたシステムに関する知識が必要な部分である。converter.length])endpwlength.timesでパスワードの長さ分だけ繰り返す。f.readbyteはurandomファイルから1バイトを読み出す。IO#readbyteは1バイトを読むのだが、返り値はその1バイトを数値で表したIntegerである。convlength先に出てきた通り、途中までで終わってしまう余りが出る部分の値である場合はやり直す。ここでnextでループを繰り返してしまうと文字数が減ってしまうのでredoでこのループをやり直す。ループをやり直しても新たに1バイトを読むようになっている(f.readbyteをもう一度通過する)から問題ない。converter.length])これも先に出てきた通りで、cがちゃんとすべての文字で公平な分配になる数であることが前行で保証されているので、配列の長さで割った余りを求めれば配列の添字として使える数だけが返る。cは/dev/urandomで得たランダムな値であるから結果的には候補文字のうちランダムな1文字が出力される。エントロピーが下がる。(暗号鍵としては十分でないが、パスワードとして十分かどうかは別問題)。Perlで実装Perlだといささかクセが強いコードになる。その理由は、Perlの独特な部分である「スカラー型とリスト型の特性」をものすごく活用することになるためだ。リスト変数をスカラーコンテキストで評価すると「リストの長さ」になるし、リストにはスカラー値しか格納できないので多次元配列にはならず、そのため「リストのリスト」は自動的に平坦なリストになったりする。すごく素直な、というか、回りくどい、というかそういう書き方になる。originであるというのが混乱を招く。あと、ライブラリに属する関数を呼ぶときは.で、インスタンスメソッド的なものは:になっているのも慣になる。endend続編この記事には続編がある。Yokohamaではプログラミングの学習に適したサービスを提供している。家庭教師サービスは(条件は限定されるが)こうした内容をより踏み込んだものや、体系化された知識を授業として受講可能だ。Code”オプションがあり、そのオプションはオーダーメイドのソフトウェアに、この記事のような解説がコメントとして入ったコードを入手することができる。これらの組み合わせも可能だ。