如何在Ruby脚本中隐藏终端中的密码输入

79

我是ruby的新手。我需要通过gets命令接收密码输入。

gets调用期间,如何隐藏在终端中键入的密码输入?


可能是如何在Ruby中从命令行读取密码?的重复问题。 - Zoran Majstorovic
6个回答

184

也可以使用核心 Ruby。

$ ri IO.noecho

 (from ruby core)
 ------------------------------------------------------------------------------
   io.noecho {|io| }
  ------------------------------------------------------------------------------

 Yields self with disabling echo back.

   STDIN.noecho(&:gets)

 will read and return a line without echo back.

对于1.9.3及以上版本,这需要您在代码中添加require 'io/console'

require 'io/console'
text = STDIN.noecho(&:gets)

21
对于1.9.3版本,这需要你在代码中添加require 'io/console'。通常比任何宝石选项都要好。 - icco
1
我无法为这个回答点赞足够多次。这比每天都要添加额外的宝石要好! - mydoghasworms
20
别忘了使用.chomp方法处理你的输入!pass = STDIN.noecho(&:gets).chomp。 (注:该代码段用于在终端中获取用户输入密码,并利用.chomp方法删除末尾的回车符号。) - Kopernik
2
你如何在Cygwin中让它工作?tmp.rb:2:in 'noecho': Bad file descriptor (Errno::EBADF) from tmp.rb:2:in <main> - Chloe

31

有一个名为highline的库,其工作方式如下:

require 'rubygems'
require 'highline/import'

password = ask("Enter password: ") { |q| q.echo = false }
# do stuff with password

24

来自@eclectic923回答的最佳方法:

require 'io/console'
password = STDIN.noecho(&:gets).chomp

对于1.9.3及以上版本,需要在您的代码中添加require 'io/console'

原始回答:

Ruby "密码"是另一种选择。


10
应该使用@eclectic923的核心Ruby解决方案,这个答案应该被弃用。 - notapatch

18

从Ruby 2.3.0版本开始,您可以使用IO#getpass方法,如下所示:

require 'io/console'
password = STDIN.getpass("Password:")

请参阅标准库文档中的getpass方法。


2
如何使用getpass打印特定字符,例如*? - João Ramires
$stdin.getpass仍会向$stdout写入一个新行。 - ShadSterling
STDIN.getpass.chomp 解决了这个问题 @ShadSterling - Olivier Lacan
哦,虽然它可能会在标准输出中打印一个换行符,但getpass在输入字符串上内部调用chomp!,所以它比其他答案更好。请参考文档返回的字符串中移除了终止读取行的换行符,请参阅String#chomp!。 - Olivier Lacan

16

如其他人所提到的,您可以在 Ruby >= 1.9 中使用 IO#noecho。如果您想要支持 1.8 版本,您可以调用内置的 shell 函数 read

begin
  require 'io/console'
rescue LoadError
end

if STDIN.respond_to?(:noecho)
  def get_password(prompt="Password: ")
    print prompt
    STDIN.noecho(&:gets).chomp
  end
else
  def get_password(prompt="Password: ")
    `read -s -p "#{prompt}" password; echo $password`.chomp
  end
end

现在获取密码就像这么简单:

@password = get_password("Enter your password here: ")
注意:在使用上面的read实现时,如果您或者get_password的另一个客户端在提示符中传递特殊的shell字符(例如$/"/'/etc),则会遇到麻烦。理想情况下,在将提示字符串传递给shell之前,您应该对其进行转义。不幸的是,Ruby 1.8中没有可用的Shellwords。幸运的是,可以轻松地将相关部分回退(具体来说,是shellescape)。有了这个,您可以进行如下微小的修改:
  def get_password(prompt="Password: ")
    `read -s -p #{Shellwords.shellescape(prompt)} password; echo $password`.chomp
  end

在下面的评论中,我提到了使用 read -s -p 存在一些问题:

嗯,1.8 版本有点不太好用;它不允许使用反斜杠,除非你输入两个反斜杠:“反斜杠字符 \ 可用于去除下一个字符的特殊含义以及行连续符。IFS 变量值中的字符用于将行分割成单词。”这对于大多数小脚本来说可能已足够,但对于更大型的应用程序,你可能需要更健壮的东西。

我们可以通过动手使用 stty(1) 来解决其中的一些问题。我们需要做以下工作:

  • 保存当前终端设置
  • 关闭回显
  • 打印提示并获取用户输入
  • 恢复终端设置

同时,我们还必须注意在信号和/或异常中断时恢复终端设置。以下代码将正确处理作业控制信号(SIGINT/SIGTSTP/SIGCONT),同时仍然与任何现有信号处理程序配合良好:


require 'shellwords'
def get_password(prompt="Password: ")
  new_sigint = new_sigtstp = new_sigcont = nil
  old_sigint = old_sigtstp = old_sigcont = nil

  # save the current terminal configuration
  term = `stty -g`.chomp
  # turn of character echo
  `stty -echo`

  new_sigint = Proc.new do
    `stty #{term.shellescape}`
    trap("SIGINT",  old_sigint)
    Process.kill("SIGINT", Process.pid)
  end

  new_sigtstp = Proc.new do
    `stty #{term.shellescape}`
    trap("SIGCONT", new_sigcont)
    trap("SIGTSTP", old_sigtstp)
    Process.kill("SIGTSTP", Process.pid)
  end

  new_sigcont = Proc.new do
    `stty -echo`
    trap("SIGCONT", old_sigcont)
    trap("SIGTSTP", new_sigtstp)
    Process.kill("SIGCONT", Process.pid)
  end

  # set all signal handlers
  old_sigint  = trap("SIGINT",  new_sigint)  || "DEFAULT"
  old_sigtstp = trap("SIGTSTP", new_sigtstp) || "DEFAULT"
  old_sigcont = trap("SIGCONT", new_sigcont) || "DEFAULT"

  print prompt
  password = STDIN.gets.chomp
  puts

  password
ensure
  # restore term and handlers
  `stty #{term.shellescape}`

  trap("SIGINT",  old_sigint)
  trap("SIGTSTP", old_sigtstp)
  trap("SIGCONT", old_sigcont)
end

嗯,1.8版本有点不稳定;它不允许使用反斜杠,除非你连续输入两次反斜杠:“反斜杠字符\可以用于删除下一个读取字符的任何特殊含义和行延续。”另外:“IFS变量值中的字符用于将行拆分为单词。”这对于大多数小脚本来说可能还好,但对于更大的应用程序,您可能需要使用更强大的工具。 - Charles

5

对于Ruby版本1.8(或Ruby < 1.9),我使用了read shell内置命令,如@Charles所提到的。

只需放置足够的代码即可提示用户名和密码,其中用户名将在输入时回显到屏幕上,但输入的密码将保持沉默。

 userid = `read -p "User Name: " uid; echo $uid`.chomp
 passwd = `read -s -p "Password: " password; echo $password`.chomp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接