Pausing until a character is received


#1

I’m struggling to figure out how to pause my script and await a single
character. I’ve been using gets which works to pause but I’d like to
do it more cleanly.

My best lead has been in this ruby quiz[1], and the reference to
STDIN.getc … however, having fumbled from a few angles, I’m still not
able to get it working the way I want.

I’d like to pause until any character is received, and be able to
gracefully rescue from an Interrupt.

The best I can do is pause on any character but ignore the interrupt.
So ^c would act like any other character… which isn’t what I want.

[1] http://www.rubyquiz.com/quiz5.html


#2

Sy Ali wrote:

I’m struggling to figure out how to pause my script and await a single
character. I’ve been using gets which works to pause but I’d like to
do it more cleanly.

My best lead has been in this ruby quiz[1], and the reference to
STDIN.getc … however, having fumbled from a few angles, I’m still not
able to get it working the way I want.

From the quiz summary:

begin
require “Win32API”
def read_char
Win32API.new(“crtdll”, “_getch”, [], “L”).Call
end
rescue LoadError
def read_char
system “stty raw -echo”
STDIN.getc
ensure
system “stty -raw echo”
end
end

I’d like to pause until any character is received, and be able to
gracefully rescue from an Interrupt.

To handle the interrupt signal, or SIGINT (but do nothing):

trap(:INT) {}

The best I can do is pause on any character but ignore the interrupt.
So ^c would act like any other character… which isn’t what I want.

I don’t understand your last paragraph. Is the code I posted what you
were after?

Cheers,
Dave


#3

On Jun 7, 2006, at 12:06 AM, Sy Ali wrote:

The best I can do is pause on any character but ignore the interrupt.
So ^c would act like any other character… which isn’t what I want.

What ^C is actually depends on your terminal. If ^C is SIGINT for the
terminal, you’ll never get ^C in the input stream no matter what you do.

(I hope I’ve understood your question)


#4

Logan C. wrote:

What ^C is actually depends on your terminal. If ^C is SIGINT for the
terminal, you’ll never get ^C in the input stream no matter what you do.

Not true - if you handle the SIGINT, there’s no reason why you
shouldn’t get a character in the input stream. Here’s an irb session
on Windows:

trap(:INT) {}
=> #<Proc:0x02855d78@C:/Documents and Settings/dburt/Program
Files/ruby/lib/ruby
/1.8/irb.rb:65>

require “Win32API”
=> true

def read_char
Win32API.new(“crtdll”, “_getch”, [], “L”).Call
end
=> nil

read_char # and press enter
=> 13

read_char # and press Ctrl+A
=> 1

read_char # and press Ctrl+C
=> 3

Cheers,
Dave


#5

On Jun 6, 2006, at 11:06 PM, Sy Ali wrote:

I’m struggling to figure out how to pause my script and await a single
character. I’ve been using gets which works to pause but I’d like to
do it more cleanly.

Grab the HighLine gem and try:

require “highline”
one_char = HighLine.new.send(:get_character)

In the next release of HighLine, this will be a public method.

James Edward G. II


#6

Thanks for the help everyone. It sounds like trap(:INT) {} will lead
to a solution that I’ll like. This is different from the combination
of getc and rescue that I was using… something new to learn. =)

HighLine would definitely be a simpler approach, but if I can do the
extra work and eliminate a dependancy I will.

I’m already thinking of re-implemeting the features of the libraries
that I’m already working with.

Of course, I seem to regularly go over my head… I guess it’s
prompting me to figure out all sorts of interesting problems.


#7

On Jun 7, 2006, at 2:17 AM, Dave B. wrote:

=> #<Proc:0x02855d78@C:/Documents and Settings/dburt/Program Files/

read_char # and press Ctrl+A
=> 1

read_char # and press Ctrl+C
=> 3

Cheers,
Dave

Aha. Well I think our respective operating systems have a difference
of opinion here. Try it on a *NIX. (Note that with a unix terminal
you could have SIGINT be ^P if you wanted).

^A (the character) will go through but ^C will not because it is
handled by the terminal, and the terminal simply does a kill(SIGINT)
on the process, it doesn’t write to the processes stdin.


#8

On 6/7/06, Dave B. removed_email_address@domain.invalid wrote:

system "stty -raw echo"

Does that work?

No, but I’ll play around some more.

undefined method `&’ for :INT:Symbol (NoMethodError)


#9

On 6/8/06, Logan C. removed_email_address@domain.invalid wrote:

Aha. Well I think our respective operating systems have a difference
of opinion here. Try it on a *NIX. (Note that with a unix terminal
you could have SIGINT be ^P if you wanted).

^A (the character) will go through but ^C will not because it is
handled by the terminal, and the terminal simply does a kill(SIGINT)
on the process, it doesn’t write to the processes stdin.

Interesting. I’m sorry for my blunt denial; I actually even expected
this might be the case.

So in that case Sy would have to explicitly write it into the input
stream if he wanted to get it out, although if he only wants to “pause
until a character is received,” there’s no need.

Something like the following (un-tested)

begin
require “Win32API”
def read_char
Win32API.new(“crtdll”, “_getch”, [], “L”).Call
end
rescue LoadError
def read_char
system “stty raw -echo”
STDIN.getc
ensure
system “stty -raw echo”
end
end

trap :INT &begin
require ‘termios’ # http://raa.ruby-lang.org/project/ruby-termios/
c = Termios.tcgetattr(STDIN).cc[Termios::VINTR]
proc { STDIN << c }
rescue LoadError
proc {}
end

Does that work?

Cheers,
Dave


#10

Sy Ali wrote:

trap :INT &begin

Try with parens:

trap(:INT, &begin
etc…)

Hal


#11

Sy Ali wrote:

trap :INT &begin

Try with parens:

trap(:INT, &begin
etc…)

Or at least a comma after the symbol.

Boy, am I tired.

Hal


#12

Well… since I can process the characters I receive through read_char
I can determine if I receive a ^c and act accordingly. Maybe this is
a smarter way to act. Maybe it’s going to bite me in the butt.

There do appear to be some quirks with using INT, which I don’t
understand. I don’t seem to be able to test this concept under my
setup.

I know my code reads like the programming equivalent to ESL, but:

begin
require “Win32API”
def read_char
Win32API.new(“crtdll”, “_getch”, [], “L”).Call
end
rescue LoadError
def read_char
system “stty raw -echo”
STDIN.getc
ensure
system “stty -raw echo”
end
end

i = 1
until i > 3
# get a character
a = read_char
# 3 is for ^c
# TODO: This may not be portable!
if a == 3 then puts “aborting…” ; break end
puts "I received: " + a.to_s
i = i + 1
end

puts “I’m done”
trap(:INT) {
puts “this doesn’t work”
}


#13

Just for kicks I tried to better understand what’s going on with trap.
Maybe I misunderstand what it’s for, but it doesn’t seem to work the
way I want. I can’t seem to properly trap based on the signal name,
and reading through some docs led me to this type of code:

Signal.trap(0, lambda { puts “interrupted…” ; exit })

Signal 0 seems to be a generic exit code. I can list the signals with
this:

Signal.list.each { |i|
print i
puts “”
}

but I don’t know the correct signal to use.

Now I’m going to give up, not just because it’s almost 3am but because
catching 3 from read_char does the job for me. If/when I end up
working with signals I’ll definitely need to revisit this stuff.


begin
require “Win32API”
def read_char
Win32API.new(“crtdll”, “_getch”, [], “L”).Call
end
rescue LoadError
def read_char
system “stty raw -echo”
STDIN.getc
ensure
system “stty -raw echo”
end
end

i = 1
until i > 3
a = read_char
if a == 3 then puts “Interrupt received. This shouldn’t be
displayed.\n … but it does.” ; break ; end
puts "I received: " + a.to_s
i += 1
end

puts “This shouldn’t display upon interrupt.”
puts " … but it always displays"

Signal.trap(3, lambda { puts “interrupted…” ; exit })


#14

Sy Ali wrote:

Just for kicks I tried to better understand what’s going on with trap.
Maybe I misunderstand what it’s for, but it doesn’t seem to work the
way I want. I can’t seem to properly trap based on the signal name,
and reading through some docs led me to this type of code:

Signals are used to asynchronously interrupt programs. For example when
you press Ctrl+C, the operating system sends SIGINT (the “interrupt”
signal) to whatever program’s running.

You can send signals programmatically, in Ruby using
Process.kill(signal, process_id), and you set signal-handlers to deal
with signals your program is sent using trap.

In the case of SIGINT, Ruby would normally raise the exception Interrupt
(a subclass of SignalException). As with any exception, if that’s not
handled, the program will terminate.

So we specify alternate behaviour with trap.

trap :INT, begin
require ‘termios’ # http://raa.ruby-lang.org/project/ruby-termios/
c = Termios.tcgetattr(STDIN).cc[Termios::VINTR]
proc { STDIN << c }
rescue LoadError
proc {}
end

The overall format is trap(:INT, …) – the return value of the
begin…end block is being passed as the handler for trap.

The simple case is just “trap(:INT, proc {}”. That means, “if I get a
SIGINT, do nothing.”

The termios stuff is only going to work on *nix and if the ruby-termios
package is installed. It gets the character that the terminal converts
into a SIGINT, and puts in on the input stream. (Because *nixes
apparently don’t send the character if it’s the SIGINT character;
Windows does.)

Either way, the main point is simply to avoid Ctrl+C crashing the
program.

but I don’t know the correct signal to use.
0 is SIGEXIT. You’ll get a SIGEXIT as your program terminates:

C:>ruby
trap(:EXIT, proc {puts “bye”})
^Z
bye

C:>

I’m tipping all you care about is SIGINT.

Now I’m going to give up, not just because it’s almost 3am but because
catching 3 from read_char does the job for me. If/when I end up
working with signals I’ll definitely need to revisit this stuff.

Interesting… it appears no trap’s necessary on Windows if you’re using
_getch. You can use ?\cC instead of 3.

puts “This shouldn’t display upon interrupt.”
puts " … but it always displays"

Signal.trap(3, lambda { puts “interrupted…” ; exit })

Your trap isn’t being added until it’s too late. While you’re in
read_char, Windows’ _getch function will return 3 rather than be
interrupted, so that works fine anyway.

Cheers,
Dave


#15

On 6/8/06, Dave B. removed_email_address@domain.invalid wrote:

Interesting… it appears no trap’s necessary on Windows if you’re using
_getch.

This keeps things simple. Nice.

You can use ?\cC instead of 3.

I’ll use that directly, but I’m not sure which would be preferred.
Either choice might not be entirely portable.

Your trap isn’t being added until it’s too late. While you’re in
read_char, Windows’ _getch function will return 3 rather than be
interrupted, so that works fine anyway.

I’ll get around to the trapping stuff when the time comes, but I
figured it was something simple like not adding it in the right order.
Making my first unit tests challenged me the same way. =)

Thanks for everything.