Ruby 1.8 世代の古いやり方だと gets 前後で
Kernel.#system から
stty (1) を呼び -echo と echo で囲むというやり方が行われており
net/imap の中でも使われていた(
※1)。
gem が使える場合には
highline って方法もあるようだが、gem を入れるのにひと手間必要(
※2)。
標準添付ライブラリの
readline を使う方法もあるようだが、自前でビルドした際にはしばしば GNU readline を入れ忘れている場合がある(
※3)。
2016-02-03 現在だと、
Ruby 1.9.3 preview1 以降で追加された標準添付ライブラリ
io/console(
code) が提供している
IO#noecho を使うのが多分一番手軽で標準的な方法のようだ(
※2,
※3)。
以下の様な感じ
require 'io/console'
print "Password: "
password = STDIN.noecho(&:gets).chomp!
puts
ただし、highline だとリアルタイムに入力を * で隠すとか出来るみたいなんだけど、
上記 IO#noecho の例だと入力は全く見えない。
これは OpenSSH なんかも、IO#noecho と同じように何も表示されないので、まぁ仕方ないかって感じ。
標準添付ライブラリ io/console が提供している
IO#getch を使うと1文字毎にインタラクト出来るので * でマスクとかも出来なくはないんだけど、F1 で \e[OP とか、DEL で \e[3~ とかの可変長エスケープシーケンスが1文字ずつ返ってくる上、Ctrl-c とかも食えちゃうのであまり楽な処理が出来ない。
例えばエスケープシーケンス無視してもこんな感じになる。Gem にするならともかく、これをソースに埋め込むのは実に野暮ったい。
#!/usr/bin/env ruby
require 'io/console'
def getpw
pw = c = ""
begin
c = STDIN.getch
case c
when /[\x03]/ # C-c
Process.kill('SIGINT', Process.pid)
when /[\x1a]/ # C-z
Process.kill('SIGSTOP', Process.pid)
when /[\b\x7f]/ # C-h|BS
if 0 < pw.length
print "\b \b"
pw.chop!
end
when /[\x15]/ # C-u
print "\b \b" * pw.length
pw = ""
when /[[:print:]]/
print "*"
pw += c
else
print "*"
pw += c
end
end until c =~ /[\r\n]/
pw
end
if __FILE__ == $0
print "Password: "
password = getpw.chomp!
puts
p password
end