Forum: Ruby pausing until a character is received

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Sy Ali (Guest)
on 2006-06-07 08:08
(Received via mailing list)
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
Dave B. (Guest)
on 2006-06-07 08:39
(Received via mailing list)
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
Logan C. (Guest)
on 2006-06-07 09:48
(Received via mailing list)
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)
Dave B. (Guest)
on 2006-06-07 10:19
(Received via mailing list)
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
James G. (Guest)
on 2006-06-07 16:56
(Received via mailing list)
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
Sy Ali (Guest)
on 2006-06-07 17:53
(Received via mailing list)
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.
Logan C. (Guest)
on 2006-06-07 21:47
(Received via mailing list)
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.
Dave B. (Guest)
on 2006-06-08 05:46
(Received via mailing list)
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
Sy Ali (Guest)
on 2006-06-08 09:41
(Received via mailing list)
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)
Hal F. (Guest)
on 2006-06-08 09:53
(Received via mailing list)
Sy Ali wrote:
>>
>> trap :INT &begin

Try with parens:

trap(:INT, &begin
   etc....)




Hal
Hal F. (Guest)
on 2006-06-08 09:53
(Received via mailing list)
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
Sy Ali (Guest)
on 2006-06-08 09:53
(Received via mailing list)
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"
}
Sy Ali (Guest)
on 2006-06-08 10:52
(Received via mailing list)
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 })
Dave B. (Guest)
on 2006-06-08 13:08
(Received via mailing list)
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
Sy Ali (Guest)
on 2006-06-08 20:15
(Received via mailing list)
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.
This topic is locked and can not be replied to.