【问题标题】:How to get a single character without pressing enter?如何在不按 Enter 的情况下获取单个字符?
【发布时间】:2010-09-15 13:16:06
【问题描述】:

如何在不按 Enter 的情况下使用 Ruby 从终端获取单个键盘字符? 我尝试了Curses::getch,但这对我来说真的不起作用。

【问题讨论】:

标签: ruby getch


【解决方案1】:

@Jay 给出了很好的答案,但是有两个问题:

  1. 你可以搞乱默认的 tty 状态;
  2. 您忽略控制字符(^C 表示 SIGINT 等)。

一个简单的解决方法是保存以前的 tty 状态并使用以下参数:

  • -icanon - 禁用规范输入(ERASE 和 KILL 处理);
  • isig - 启用对特殊控制字符 INTR、QUIT 和 SUSP 的字符检查。

最后你会得到这样的功能:

def get_char
  state = `stty -g`
  `stty raw -echo -icanon isig`

  STDIN.getc.chr
ensure
  `stty #{state}`
end

【讨论】:

    【解决方案2】:

    注意:这是旧答案,该解决方案不再适用于大多数系统。

    但是对于其他方法不起作用的某些环境,答案可能仍然有用。请阅读下面的cmets。


    首先你必须安装highline:

    gem install highline
    

    然后试试 highline 方法是否适合你:

    require "highline/system_extensions"
    include HighLine::SystemExtensions
    
    print "Press any key:"
    k = get_character
    puts k.chr
    

    【讨论】:

    • 警告:最近的一次提交从 highline 中删除了此功能。见github.com/JEG2/highline/issues/50
    • 这不起作用:它需要您按 键。顺便说一句,它还包含 \n 字符。
    • 这个 example 在 Windows 7 上的 Ruby 2.0 上为我工作。
    • 在 Ruby 1.9.3 和 Windows XP 上工作,无需按 Enter(在 pry 上测试)。 ps 谢谢!
    【解决方案3】:

    从 ruby​​ 2.0.0 开始,stdlib 中有一个具有此功能的“io/console”

    require 'io/console'
    STDIN.getch
    

    【讨论】:

    • 效果很好,前提是您事后检查 control-C:exit(1) if char == "\u0003"
    • 我什至不需要require :)
    • @philant nope,我有一个脚本并通过$ ruby script.rb 调用它。
    • 这可能是当今最好的答案,因为如果必须更改功能,那么 ruby​​ 核心团队(例如 nobu)可以修改/调整它。从长远来看,这应该会更好。
    【解决方案4】:

    如果您正在构建 curses 应用程序,则需要调用

    nocbreak
    

    http://www.ruby-doc.org/stdlib-1.9.3/libdoc/curses/rdoc/Curses.html#method-c-cbreak

    【讨论】:

      【解决方案5】:

      Raw 模式 (stty raw -echo) 不幸地导致 control-C 作为字符而不是 SIGINT 发送。因此,如果您想像上面那样阻止输入,但允许用户在等待时按 control-C 来停止程序,请确保这样做:

      Signal.trap("INT") do # SIGINT = control-C
        exit
      end
      

      如果你想要非阻塞输入——也就是说,定期检查用户是否按下了某个键,但与此同时,去做其他事情——那么你可以这样做:

      require 'io/wait'
      
      def char_if_pressed
        begin
          system("stty raw -echo") # turn raw input on
          c = nil
          if $stdin.ready?
            c = $stdin.getc
          end
          c.chr if c
        ensure
          system "stty -raw echo" # turn raw input off
        end
      end
      
      while true
        c = char_if_pressed
        puts "[#{c}]" if c
        sleep 1
        puts "tick"
      end
      

      请注意,非阻塞版本不需要特殊的 SIGINT 处理程序,因为 tty 仅在短时间内处于原始模式。

      【讨论】:

      • 这个答案很棒,正是我想要的。非常感谢功能异常强大的代码!
      • Signal.trap('INT') { exit } 使程序在按下 control-C 时退出(除非它被其他方式绕过)并且肯定在 1.9.2-p290 中有效,所以我不知道什么“不起作用”对我来说”可能意味着
      • 这在您的示例中有效,但如果您使用STDIN.getch,它肯定不起作用。然后你的 SIGINT 被解释为"\u0003"。或者可能是其他东西,具体取决于您的终端。
      【解决方案6】:

      http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/2999

      #!/usr/bin/ruby
      
      begin
        system("stty raw -echo")
        str = STDIN.getc
      ensure
        system("stty -raw echo")
      end
      p str.chr
      

      (在我的 OS X 系统上测试,可能无法移植到所有 Ruby 平台)。请参阅 http://www.rubyquiz.com/quiz5.html 了解其他建议,包括针对 Windows 的建议。

      【讨论】:

      • 到目前为止有效,但不幸的是我得到的是数字而不是字符串。你知道如何将数字转换为正确的 ascii 字符吗?
      • 可以,用str.chr获取数值对应的字符即可。我已经更新了帖子以反映这一点
      • 不幸的是,由于您处于原始模式,因此 control-C 作为字符而不是 SIGINT 被发送。因此,如果您想像上面那样阻止输入,但仍想让用户在等待时按 control-C 来停止程序,请确保这样做:Signal.trap("INT") { exit }(请参阅下面的答案以获得更好的格式版本)跨度>
      • Andrew 下面的答案(2013 年 1 月 25 日 17:49)更好。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-01-08
      • 2021-03-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-02
      相关资源
      最近更新 更多