miauのブログ

はてなダイアリー「miauの避難所」をはてなブログに移行しました

パスワードを生成するワンライナー

仕事でたまにランダムっぽいパスワードを生成する必要が出てきたりします。今までは

perl -e "@c = (ord('!') .. ord('~')); for (1..10) {print chr($c[rand() * @c]);}"

こんな感じのワンライナーを使っていたんですけど、WindowsLinux の両方で動作しなかったり、文字種を変えたりするのが面倒だったので、改良を加えてみることにしました。


ググるといくつか出てきて、

perl -le 'print map { ("a".."z", 0..9)[rand 36] } 1..8'

これはシンプルでいい感じ。

perl -e 'for (1..100) { for (1..8) { print((0..9,a..z,A..Z)[int rand 62])} print "\n" }'

なんで "a".."z" じゃなくて a..z って書けるんだっけ?と思ったけど、場所によっては予約語とみなされるっぽいですね。-w オプションだと警告でるけど、ワンライナーで使うぶんにはこのほうが便利そうです。

このあたりを参考にしつつ、

  • Windows でも Linux でも動く
    • 具体的には、コード部はダブルクォートで囲む&Linux で実行できるようにコード内で $ を使わない
  • 文字種を変更した場合も 36 とか 62 とかの部分を変更しなくていい

という条件を満たす範囲で書き換えてみました。(map より for のほうが見やすいのでそこも変更。)

perl -e "@c = (a..z, 0..9); print @c[rand @c] for 1..8"

冒頭の例に比べるとずいぶんすっきりしました。

ただ、範囲演算子

'!'..'~'

みたいに記号を書くことはできないようで(だから冒頭の例ではわざわざ ord を使っていたみたい)、「記号は全部許可する」というような要望が出た場合に、すべての記号を羅列するのは面倒な気がします。

アスキーコードの全集合を作っておいてフィルタリングするほうが文字種を変更しやすくていいんじゃないかな?ということで書き直したのがこちら。

perl -e "@c = grep {/[a-z0-9]/} map(chr, 0..0x7e); print @c[rand @c] for 1..8"

これで十分保守性は高まったけど、他の言語やパラダイムを使えばもっとすっきり書けそうな気も・・・。何かいい手があれば教えて下さいませ。

      • -

追記(蛇足)

使用できる文字種の一覧を最初に作るんじゃなくて、遅延評価っぽくその都度 rand &フィルタリング&再取得を実行する処理を、なぜか Ruby で書いてみた。

ruby -e "8.times do print lambda { rand(0x80).chr.match(/[a-z0-9]/) || redo}.call end"

例えば「Unicode の特定の文字が使える」みたいに母集団が大きい場合にはこういう手法も有効・・・なのかなぁ。