gets.chomp VS $stdin.gets.chomp
Published on 02 Dec 2016
by Alexander Garber
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:
The simplest explanation I found for the difference between gets.chomp and $stdin.gets.chomp is this, from Stack Overflow:
gets.chomp() = read ARGV firstA further clarification:
STDIN.gets.chomp() = read user's input
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:
Take note that I put gets.chomp and $stdin.gets.chomp and uncommented the one I wanted to test.
# 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
To this I added two text files for testing:
- ex_15_sample.txt
- 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:
- The first part of the script read the input file from ARGV.
- The second part of the script read as input the first line of the input file from ARGV.
Conclusions
- If you want to prompt the user for input, use $stdin.gets.chomp.
- 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.