在Ruby中检测没有getc/get的按键(非阻塞)

Rob*_*t K 19 ruby input nonblocking

我有一个简单的任务,需要等待文件系统上的某些东西改变(它本质上是原型的编译器).因此,在检查更改的文件后,我有一个简单的无限循环,睡眠时间为5秒.

loop do
  # if files changed
  #   process files
  #   and puts result
  sleep 5
end
Run Code Online (Sandbox Code Playgroud)

而不是Ctrl+C敬礼,我宁愿能够测试并看看是否按下了一个键,而没有阻止循环.基本上我只需要一种方法来判断是否有传入的按键,然后一种方法来获取它们直到满足Q,然后退出程序.

我想要的是:

def wait_for_Q
  key_is_pressed && get_ch == 'Q'
end

loop do
  # if files changed
  #   process files
  #   and puts result
  wait_for_Q or sleep 5
end
Run Code Online (Sandbox Code Playgroud)

或者,这是Ruby不做的事情(好)?

ram*_*ion 13

这是一种方法,使用IO#read_nonblock:

def quit?
  begin
    # See if a 'Q' has been typed yet
    while c = STDIN.read_nonblock(1)
      puts "I found a #{c}"
      return true if c == 'Q'
    end
    # No 'Q' found
    false
  rescue Errno::EINTR
    puts "Well, your device seems a little slow..."
    false
  rescue Errno::EAGAIN
    # nothing was ready to be read
    puts "Nothing to be read..."
    false
  rescue EOFError
    # quit on the end of the input stream
    # (user hit CTRL-D)
    puts "Who hit CTRL-D, really?"
    true
  end
end

loop do
  puts "I'm a loop!"
  puts "Checking to see if I should quit..."
  break if quit?
  puts "Nope, let's take a nap"
  sleep 5
  puts "Onto the next iteration!"
end

puts "Oh, I quit."
Run Code Online (Sandbox Code Playgroud)

请记住,即使这使用非阻塞IO,它仍然是缓冲 IO.这意味着,用户将不得不打Q那么<Enter>.如果你想做无缓冲的IO,我建议你查看ruby的curses库.


小智 10

你也可以在没有缓冲区的情况下这样做.在基于unix的系统中,很容易:

system("stty raw -echo") #=> Raw mode, no echo
char = STDIN.getc
system("stty -raw echo") #=> Reset terminal mode
puts char
Run Code Online (Sandbox Code Playgroud)

这将等待按下一个键并返回char代码.无需按.

把它char = STDIN.getc放进一个循环中就可以了!

如果你在Windows上,根据The Ruby Way,你需要在C中编写扩展或使用这个小技巧(尽管这是在2001年写的,所以可能有更好的方法)

require 'Win32API'
char = Win32API.new('crtdll','_getch', [], 'L').Call
Run Code Online (Sandbox Code Playgroud)

这是我的参考:好书,如果你不拥有它,你应该

  • 我不明白.这种非阻塞是怎样的?它等待char. (4认同)

小智 9

其他答案的组合获得所需的行为.在OSX和Linux上测试ruby 1.9.3.

loop do
  puts 'foo'
  system("stty raw -echo")
  char = STDIN.read_nonblock(1) rescue nil
  system("stty -raw echo")
  break if /q/i =~ char
  sleep(2)
end
Run Code Online (Sandbox Code Playgroud)


小智 7

通过结合我刚刚阅读的各种解决方案,我提出了一种解决该问题的跨平台方法. 这里有详细介绍,但这里是相关的代码段:GetKey.getkey返回ASCII代码的方法或者nil没有按下的代码.

应该在Windows和Unix上都可以使用.

module GetKey

  # Check if Win32API is accessible or not
  @use_stty = begin
    require 'Win32API'
    false
  rescue LoadError
    # Use Unix way
    true
  end

  # Return the ASCII code last key pressed, or nil if none
  #
  # Return::
  # * _Integer_: ASCII code of the last key pressed, or nil if none
  def self.getkey
    if @use_stty
      system('stty raw -echo') # => Raw mode, no echo
      char = (STDIN.read_nonblock(1).ord rescue nil)
      system('stty -raw echo') # => Reset terminal mode
      return char
    else
      return Win32API.new('crtdll', '_kbhit', [ ], 'I').Call.zero? ? nil : Win32API.new('crtdll', '_getch', [ ], 'L').Call
    end
  end

end
Run Code Online (Sandbox Code Playgroud)

这是一个简单的程序来测试它:

loop do
  k = GetKey.getkey
  puts "Key pressed: #{k.inspect}"
  sleep 1
end
Run Code Online (Sandbox Code Playgroud)

在上面提供的链接中,我还展示了如何使用该curses库,但结果在Windows上有点令人讨厌.