A blog about software by Alexander YaƤkov Garber.

gets.chomp VS $stdin.gets.chomp

Whilst working on Exercise 15 of Learn Ruby the Hard Way, the author prompted me to ponder the difference between gets.chomp and $stdin.gets.chomp.

The simplest explanation I found for the difference between gets.chomp and $stdin.gets.chomp is this, from Stack Overflow:
gets.chomp() = read ARGV first

STDIN.gets.chomp() = read user's input
A further clarification:
because if there is stuff in ARGV, the default gets method tries to treat the first one as a file and read from that. To read from the user's input (i.e., stdin) in such a situation, you have to use it STDIN.gets explicitly.

Source Code from Exercise 15 of Learn Ruby the Hard Way:


# Get the input file
filename = ARGV.first

# Declare a variable to open the input file
txt = open(filename)

# Output the contents of the input file
puts "Here's your file #{filename}"
print txt.read

# Ask the user to name the input file, implicitly in the local directory
print "Type the filename again: "

# Use stdin to obtain the name of the input file from the local directory
file_again = $stdin.gets.chomp
# file_again = gets.chomp

# Declare another variable to open the input file again
txt_again = open(file_again)

# Output the contents of the input file again
print txt_again.read
 Take note that I put gets.chomp and $stdin.gets.chomp and uncommented the one I wanted to test.

To this I added two text files for testing:
  1. ex_15_sample.txt
  2. ex_15_sample_2.txt

ex_15_sample.txt:

This is stuff I typed into a file.

It is really cool stuff.

Lots and lots of fun to have in here.

ex_15_sample_2.txt

THIS IS STUFF I TYPED INTO A FILE.

IT IS REALLY COOL STUFF.

LOTS AND LOTS OF FUN TO HAVE IN HERE.

Scenario 1: $stdin.gets.chomp

Script:


# Get the input file
filename = ARGV.first

# Declare a variable to open the input file
txt = open(filename)

# Output the contents of the  input file
puts "Here's your file #{filename}"
print txt.read

# Ask the user to name the input file, implicitly in the local directory
print "Type the filename again: "

# Use stdin to obtain the name of the input file from the local directory
file_again = $stdin.gets.chomp
# file_again = gets.chomp

# Declare another variable to open the input file again
txt_again = open(file_again)

# Output the contents of the input file again
print txt_again.read

Command in terminal:

$ ruby ex15.rb ex_15_sample.txt

Output:

Here's your file ex_15_sample.txt

This is stuff I typed into a file.

It is really cool stuff.

Lots and lots of fun to have in here.

Type the filename again: ex_15_sample_2.txt

THIS IS STUFF I TYPED INTO A FILE.

IT IS REALLY COOL STUFF.

LOTS AND LOTS OF FUN TO HAVE IN HERE.

Scenario 2: gets.chomp

Script:

# Get the input file

filename = ARGV.first

# Declare a variable to open the input file

txt = open(filename)

# Output the contents of the  input file

puts "Here's your file #{filename}"

print txt.read

# Ask the user to name the input file, implicitly in the local directory

print "Type the filename again: "

# Use stdin to obtain the name of the input file from the local directory

# file_again = $stdin.gets.chomp

file_again = gets.chomp

# Declare another variable to open the input file again

txt_again = open(file_again)

# Output the contents of the input file again

print txt_again.read

Command in terminal:

$ ruby ex15.rb ex_15_sample.txt

Output:

Here's your file ex_15_sample.txt

This is stuff I typed into a file.

It is really cool stuff.

Lots and lots of fun to have in here.

Type the filename again: ex15.rb:20:in `initialize': No such file or directory @ rb_sysopen - This is stuff I typed into a file. (Errno::ENOENT)

from ex15.rb:20:in `open'

from ex15.rb:20:in `<main>'
Let's look a little more closely at what went wrong.  

The first part of the script took the argument from the commandline and read the contents of the file ex_15_sample.txt -- great!

Then it printed "Type the filename again." -- again, that's what we want.

However, when it got to gets.chomp, it attempted to take as an input the first line of the contents of the file denoted by ARGV.

Let's look at the contents of the text file in question:

ex_15_sample.txt:

This is stuff I typed into a file.

It is really cool stuff.

Lots and lots of fun to have in here.
Do you see what Ruby tried to do here?  gets.chomp already has the filename from ARGV, but instead of reading the filename, it reads the first line of the file.

Scenario 3: gets.chomp with a hacked input file

If gets.chomp take the first line of the file named by ARGV, what would happen if I hack the text file and put the name of the second file in the first line?

ex_15_sample_hacked.txt:

ex_15_sample_2.txt

This is stuff I typed into a file.

It is really cool stuff.

Lots and lots of fun to have in here.

Script:


# Get the input file
filename = ARGV.first

# Declare a variable to open the input file
txt = open(filename)

# Output the contents of the input file
puts "Here's your file #{filename}"
print txt.read

# Ask the user to name the input file, implicitly in the local directory
print "Type the filename again: "

# Use stdin to obtain the name of the input file from the local directory
# file_again = $stdin.gets.chomp
file_again = gets.chomp

# Declare another variable to open the input file again
txt_again = open(file_again)

# Output the contents of the input file again
print txt_again.read


Command in terminal:

$ ruby ex15.rb ex_15_sample_hacked.txt 

Output:

Here's your file ex_15_sample_hacked.txt

ex_15_sample_2.txt

This is stuff I typed into a file.

It is really cool stuff.

Lots and lots of fun to have in here.

Type the filename again: THIS IS STUFF I TYPED INTO A FILE.

IT IS REALLY COOL STUFF.

LOTS AND LOTS OF FUN TO HAVE IN HERE.

Observations:

  1. The first part of the script read the input file from ARGV.
  2. The second part of the script read as input the first line of the input file from ARGV.

Conclusions

  1. If you want to prompt the user for input, use $stdin.gets.chomp.
  2. If you want to trick the program into using the first line of the first file supplied to ARGV, gets.chomp is an option, but a better way to do it would simply be to declare the variable in your code.

all tags