Stephen
Stephen

Reputation: 6087

Multi-line input problems when using STDIN.gets

Having looked at this question, I have the following code:

$/ = "\0"
answer = STDIN.gets

Now, I was hoping that this would allow the user to:

However, the behaviour I actually see is that:

So, why does the single line situation (i.e. if the user has entered some text but no newline and then hit Ctrl-D) require two presses of Ctrl-D? And why does it work then if the user enters nothing? (I have noted that if they enter nothing and hit Ctrl-D, I don't get an empty string but the nil class - I discovered this when trying to call .empty? on the result, since it suddenly failed horribly. If there is a way to get it to return an empty string as well, that would be nice. I prefer checking .empty? to ==, and don't particularly want to define .empty? for the nil class.)

EDIT: Since I really would like to know the "correct way" to do this in Ruby, I am offering a bounty of 200 rep. I will also accept answers that give another way of entering terminal multi-line input with a sensible "submit" procedure - I will be the judge of 'suitable'. For example, we're currently using two "\n"s, but that's not suitable, as it blocks paragraphs and is unintuitive.

Upvotes: 3

Views: 3901

Answers (2)

Jason
Jason

Reputation: 1138

The basic problem is the terminal itself. See many of the related links to the right of your post. To get around this you need to put the terminal in a raw state. The following worked for me on a Solaris machine:

#!/usr/bin/env ruby
# store the old stty settings
old_stty = `stty -g`
# Set up the terminal in non-canonical mode input processing
# This causes the terminal to process one character at a time
system "stty -icanon min 1 time 0 -isig"
answer = ""
while true
  char = STDIN.getc
  break if char == ?\C-d # break on Ctrl-d
  answer += char.chr
end
system "stty #{old_stty}" # restore stty settings
answer

I'm not sure if the storing and restoring of the stty settings is necessary but I've seen other people do it.

Upvotes: 2

Steve Weet
Steve Weet

Reputation: 28392

When reading STDIN from a terminal device you are working in a slightly different mode to reading STDIN from a file or a pipe.

When reading from a tty Control-D (EOF) only really sends EOF if the input buffer is empty. If it is not empty it returns data to the read system call but does not send EOF.

The solution is to use some lower level IO and read a character at a time. The following code (or somethings similar) will do what you want

#!/usr/bin/env ruby

answer = ""
while true
  begin
    input = STDIN.sysread(1)
    answer += input
  rescue EOFError
    break
  end
end

puts "|#{answer.class}|#{answer}|"

The results of running this code with various inputs are as follows :-

INPUT This is a line<CR><Ctrl-D>

|String|This is a line
|

INPUT This is a line<Ctrl-D>

|String|This is a line|

INPUT<Ctrl-D>

|String||

Upvotes: 2

Related Questions