How to get a single character without pressing enter? - ruby

How can I get a single keyboard character from the terminal with Ruby without pressing enter?
I tried Curses::getch, but that didn't really work for me.

Since ruby 2.0.0, there is a 'io/console' in the stdlib with this feature
require 'io/console'
STDIN.getch

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/2999
#!/usr/bin/ruby
begin
system("stty raw -echo")
str = STDIN.getc
ensure
system("stty -raw echo")
end
p str.chr
(Tested on my OS X system, may not be portable to all Ruby platforms). See http://www.rubyquiz.com/quiz5.html for some additional suggestions, including for Windows.

#Jay gave a great answer, but there are two problems:
You can mess up default tty state;
You ignore control characters (^C for SIGINT, etc).
A simple fix for that is to save previous tty state and use following parameters:
-icanon - disable canonical input (ERASE and KILL processing);
isig - enable the checking of characters against the special control characters INTR, QUIT, and SUSP.
In the end you would have a function like this:
def get_char
state = `stty -g`
`stty raw -echo -icanon isig`
STDIN.getc.chr
ensure
`stty #{state}`
end

Raw mode (stty raw -echo) unfortunately causes control-C to get sent in as a character, not as a SIGINT. So if you want blocking input like above, but allow the user to hit control-C to stop the program while it's waiting, make sure to do this:
Signal.trap("INT") do # SIGINT = control-C
exit
end
And if you want non-blocking input -- that is, periodically check if the user has pressed a key, but in the meantime, go do other stuff -- then you can do this:
require 'io/wait'
def char_if_pressed
begin
system("stty raw -echo") # turn raw input on
c = nil
if $stdin.ready?
c = $stdin.getc
end
c.chr if c
ensure
system "stty -raw echo" # turn raw input off
end
end
while true
c = char_if_pressed
puts "[#{c}]" if c
sleep 1
puts "tick"
end
Note that you don't need a special SIGINT handler for the non-blocking version since the tty is only in raw mode for a brief moment.

Note: This is and old answer and the solution no longer works on most systems.
But the answer could still be useful for some environments, where the other methods don't work. Please read the comments below.
First you have to install highline:
gem install highline
Then try if the highline method works for you:
require "highline/system_extensions"
include HighLine::SystemExtensions
print "Press any key:"
k = get_character
puts k.chr

And if you are building curses application, you need to call
nocbreak
http://www.ruby-doc.org/stdlib-1.9.3/libdoc/curses/rdoc/Curses.html#method-c-cbreak

Related

Thread ignoring first input when using getch

require 'rubygems'
require 'mechanize'
require 'io/console'
flag = 0
t2 =Thread.new do
puts flag
loop do
temp = STDIN.getch
if temp=="\n"
flag = (flag+1)%2
puts flag
end
end
end
# => Some foreground code
t2.join
When i run the code i get the value of flag printed 0 as it should. But the thread does not change the value of flag on the first Enter I hit. Hitting Enter the second time changes flag to 1 although. The thread works normally toggling the value of flag on further Enter hits. Why is this happening? What have I done wrong?
Problem seems to be only with getch
as when I use gets in place of getch the problem disappears. But I cant use gets because I want the user to hit a single key without needing to press Enter after the key to give input.
For example flag should not change when the user inputs a instead of Enter and so I have used getch to make sure the input is given after a single keyboard hit.
A similar problem was described here but it isn't a duplicate.
Edit 1:
The problem seems to be with getch and not the check what do ever.
flag = 0
t2 =Thread.new do
puts flag
loop do
temp = STDIN.getch
flag = (flag+1)%2
puts flag
end
end
t2.join
Even after removing the if statement, the first Enter is ignored no matter what but other characters seem to respond to the first time. The problem is coming only when I hit Enter. It doesn't count the first Enter I hit.
ruby 2.3.3p222 (2016-11-21 revision 56859) [x64-mingw32]
I tried your code on a Windows machine and was able to re-create the problem. As you correctly guessed it has nothing to do with threading and everything with how getch works (on Windows).
If you add a p temp.inspect to your loop you will see that it is not so much that the first '\n' is swallowed, rather it is that it is somehow "held back". The best way to see this if you press Enter and another key alternatively. You will see that the inspect is "off-by-one".
A good explanation about the problem is discussed here:
https://www.rubytapas.com/2016/12/14/ruby-code-on-windows/
With that information, a simple solution (that has the added benefit to run also on Linux, not sure about Mac) is:
require 'rubygems'
require 'mechanize'
require 'io/console'
STDIN.binmode #this line added to prevent line-end translation
flag = 0
t2 =Thread.new do
puts flag
loop do
temp = STDIN.getch
if temp=="\r" # note: changed from LF to CR
flag = (flag+1)%2
puts flag
end
end
end
# => Some foreground code
t2.join
Notes:
Admittedly I don't fully get the way it works. I was expecting that Enter would cause a "\r\n" sequence in binmode, but I only see "\r". Not sure what happens to the "\n", but it seems to work reliably this way.
Also note that in the current version the program can not be terminated with Ctrl+C. You'll have to add a check for that.

Running cmd commands through ruby

I am writing a program which execute an other program written in c, here is my first try
require 'Open3'
system 'tcc temp.c'
Open3.popen3('temp.exe') do |stdin, stdout, stderr|
stdin.puts '21\n'
STDOUT.puts stdout.gets
end
actual output:
Enter the temperature in degrees fahrenheit: The converted temperature is -6.11
desired output:
Enter the temperature in degrees fahrenheit: 21
The converted temperature is -6.11
and if you know a better way to do that please tell me, i am new to ruby.
You seem to have at least two potential issues:
Your newline will not expand inside single quotes. To include a newline within a string, you need to use double-quotes such as "21\n".
In some cases, you actually need a carriage return rather than a newline. This is especially true when trying to do Expect-like things with a terminal. For example, you may find you need \r instead of \n in your string.
You definitely need to fix the first thing, but you may need to try the second as well. This is definitely one of those "your mileage may vary" situations.
It seems like you're expecting 21 to appear on your screen because it does when you run temp.exe and type in 21. The reason it appears on your screen under those circumstances is that you're typing them into your shell, which "echoes" back everything you type.
When you run the program via Ruby, though, there's no shell and no typing, so 21 doesn't appear on your screen even though it's correctly being sent to the program's standard input.
The simplest solution is pretty simple. Just write it to Ruby's standard output as well:
require 'Open3'
system 'tcc temp.c'
Open3.popen3('temp.exe') do |stdin, stdout, stderr|
STDOUT.puts "21"
stdin.puts '"21"
STDOUT.puts stdout.gets
end
(You'll note that I took out \n—IO#puts adds that for you.)
This is a little repetitive, though. You might define a simple method to take care of it for you:
def echo(io, *args)
puts *args
io.puts *args
end
Then:
Open3.popen3('temp.exe') do |stdin, stdout, stderr|
echo(stdin, "21")
puts stdout.gets
end

How to tell STDIN to stop reading?

So I am studying Build Awesome Command-Line Applications in Ruby. On page 81, we're supposed to use STDIN to enter more than one task into a project.
File.open(global_options[:filename], 'a+') do |todo_file|
if task_names.empty?
puts "Reading new tasks from stdin..."
task_names = STDIN.readlines.map {|a| a.chomp}
end
tasks = 0
task_names.each do |task|
todo_file.puts [task, Time.now].join(', ')
tasks+=1
end
if tasks == 0
raise "You must provide tasks on the command-line or standard input"
end
end
The usual way to enter tasks into a project it's like this $todo new "Rake leaves but with the code above we can to what's in the example below.
It does work. But how do I tell STDIN to stop listening? The example on how to use it is this...
$ todo new
Rake leaves
Take out trash
Clean garage
Put away dishes
^D
What does the ^D represent?
It’s an end-of-file character. You can type this literally on Unix systems with Ctrl+D or on Windows with Ctrl+Z. The traditional way of displaying the Ctrl modifier is with a ^ prefix, e.g., ^D.
Be aware that this closes standard input entirely. If you want to read more data after entering these lines, you’ll need to check the input itself for a different delimiter—for instance, an empty line.
You can close STDIN by pressing Ctrl-d on Unix-like systems or Ctrl-z on Windows.
What does the ^6 represent?
Are you sure it says ^6 and not ^d? If so, that's probably a typo.

Calling a Perl script from Ruby

I am currently attempting to figure out a way to call a Perl script from Ruby and have it output as if I was in the terminal and would allow me to provide input if it is needed.
I have figured out how I can do this and get the input after the fact but because the Perl script is still running, I am not able to run anything else.
I should note that I can not edit the Perl scripts. These scripts are being provided and this Ruby script is being made to make the process of running all of the Perl scripts easier and ensuring they are in the right order.
upgradestatus = `#{upgradearray[arraylocation]}`
This would be the relevant part my code for this. I have attempted a few other variations of how to do this but I get the same situation every time. When the script starts running it requires input so it just sits there.
You can't do what you want using backticks, %x or as a normal sub-shell, because they lack the ability to watch the output of the sub-command's output.
You could do it using Open3's popen2 or popen3 methods. They let you send to the STDIN stream for the called program, and receive data from the STDOUT. popen3 also lets you see/capture the STDOUT stream too. Unfortunately, often you have to send, then close the STDIN stream before the called program will return its information, which might be the case of the Perl scripts.
If you need more control, look into using Ruby's built-in Pty module. It's designed to let you talk to a running app through a scripting mechanism. You have to set up code to look for prompts, then respond to them by sending back the appropriate data. It can be simple, or it can be a major PITA, depending on the code you're talking to.
This is the example for the open command:
PTY.open {|m, s|
p m #=> #<IO:masterpty:/dev/pts/1>
p s #=> #<File:/dev/pts/1>
p s.path #=> "/dev/pts/1"
}
# Change the buffering type in factor command,
# assuming that factor uses stdio for stdout buffering.
# If IO.pipe is used instead of PTY.open,
# this code deadlocks because factor's stdout is fully buffered.
require 'io/console' # for IO#raw!
m, s = PTY.open
s.raw! # disable newline conversion.
r, w = IO.pipe
pid = spawn("factor", :in=>r, :out=>s)
r.close
s.close
w.puts "42"
p m.gets #=> "42: 2 3 7\n"
w.puts "144"
p m.gets #=> "144: 2 2 2 2 3 3\n"
w.close
# The result of read operation when pty slave is closed is platform
# dependent.
ret = begin
m.gets # FreeBSD returns nil.
rescue Errno::EIO # GNU/Linux raises EIO.
nil
end
p ret #=> nil

How should I check for EOF in Ruby

I have a Ruby script (1.8.7) that sets up its own interactive shell for running specific commands.
I want to be able to exit when a user presses CTRL+D (mac/linux). The script just sits in a loop and uses Readline to read user input. I understand that CTRL+D sends the EOF control character but how do I test for this in Ruby? It doesn't seem to be included in the lists of standard unix signals and since technically it isn't a character, I'm guessing normal string comparison on the line won't work either.
Any ideas / pointers / suggestions would be much appreciated
Cheers
From the documentation:
readline(prompt = "", add_hist = false)
(…) Returns nil when the inputted line is empty and user inputs EOF (Presses ^D on UNIX).
Example:
require "readline"
while buf = Readline.readline("> ", true)
p buf
end
puts "EOF received, exiting"

Resources