Could gets be a loop condition? - ruby

Wanted to make a loop asking the user for input each time and break the loop as soon as the input is empty.
lines << line while line = gets.chomp
The code above fails to break the loop. Using the irb and putting in nothing showed that the condition doesn't return a nil:
irb(main):001:0> line = gets.chomp
=> ""
Is there a way to get it work?

The problem that in you sample while loop will break when line is null, but gets.chomp will return empty string when empty line is given.
Simplest solution to use loop with explicit break
lines = []
loop do
line = gets.chomp
break if line.empty?
lines << line
end
If you would use ActiveSupport library(included with Rails) you can do one liner with presence method
lines << line while line = gets.chomp.presence

Related

How do I receive multiple paragraphs from a user in Ruby2.6.3?

I'm trying to receive multiple paragraphs at once from a user.
I've tried using gets, but it doesn't seem to be working... it discards the second paragraph:
#The code:
print("Paste your text here: ")
.. essay = gets
.. puts(essay)
# Getting user imput (the second sentance is a separate paragraph)
Paste your text here: I like cake.
It makes me happy.
# What the computer did for puts(essay):
I like cake.
=> nil
I expected the result to be something like this:
"I like cake.\nIt makes me happy.\n"
But it gave me "I like cake." instead.
How could I end up with my expected result?
Add paragraphs to a string until the input consists of a empty line:
str = ""
para = "init"
str << (para = gets) until para.chomp.empty? #or para == "\n"
p str
Here's an alternative, with a slightly different logic
def getps
save, $/ = $/, "\n\n"
gets.chomp
ensure
$/ = save
end
str = getps
The global variable $/ is what Ruby uses to find out what line end is. gets gets things till line end. If we tell Ruby that line end is two newlines, then gets waits till we have two newlines in a row till it exits. Since we don't need them, we'll just chomp them off. The rest of the code is just to ensure that $/ gets restored properly afterwards so normal gets is not messed up forever.

In Ruby, why aren't variables not interchangeable within code blocks?

I have a file called "file1.txt":
Ruby
programming
is fun
In files.rb, which I'm calling from IRB, I have:
File.open('file1.txt', 'r') do |file|
while line = file.gets
puts "** " + line.chomp + " **" #--> why can't I use file.gets.chomp?
end
end
Why isn't line and file.gets interchangeable on line 3? If I switch line with file.gets, the function does not work, and I am a little bit perplexed considering that
line = file.gets
and
file.gets = line
should be interchangeable, but in this case, it is not as it gives me an error. The function works with line.chomp.
I tried getting rid of the while code block, and simply writing
puts file.gets
and it seems to output a line of code from file1.txt, but does not work inside the while statement on line 3.
I'm not really into Ruby, but I think that is because if you use while line = file.gets, the file.gets return a line and read (and copy to buffer) the next one. In the final iteration, where the while is in the last line, the while line = file.gets will return the last line. But in the while, you call again file.gets, so as there are no more lines in file, it returns an error.
This is untested, but your code can be reduced to:
File.foreach('file1.txt') do |line|
puts "** " + line + " **"
end

Regular Expression matching in ruby, checking for white space

I am trying to check a file for white spaces at the beginning of each line. I want the white-space at the beginning of the line to be consistent, all start with spaces or all start with tabs. I wrote the code below but it isn't working for me. If there exist a space at a beginning of one line and then a tab exists in the beginning of another line print a warning or something.
file = File.open("file_tobe_checked","r") #I'm opening the file to be checked
while (line = file.gets)
a=(line =~ /^ /).nil?
b=(line =~/^\t/).nil?
if a==false && b==false
print "The white spaces at the beginning of each line are not consistent"
end
end
file.close
This is one solution where you don't read the file or the extracted lines array twice:
#!/usr/bin/env ruby
file = ARGV.shift
tabs = spaces = false
File.readlines(file).each do |line|
line =~ /^\t/ and tabs = true
line =~ /^ / and spaces = true
if spaces and tabs
puts "The white spaces at the beginning of each line are not consistent."
break
end
end
Usage:
ruby script.rb file_to_be_checked
And it may be more efficient to compare lines with these:
line[0] == "\t" and tabs = true
line[0] == ' ' and spaces = true
You can also prefer to use each_line over readlines. Perhaps each_line allows you to read the file line by line instead of reading all the lines in one shot:
File.open(file).each_line do |line|
How important is it that you check for the whitespace (and warn/notify accordingly)? If you are aiming to just correct the whitespace, .strip is great at taking care of errant whitespace.
lines_array = File.readlines(file_to_be_checked)
File.open(file_to_be_checked, "w") do |f|
lines_array.each do |line|
# Modify the line as you need and write the result
f.write(line.strip)
end
end
I assume that no line can begin with one or more spaces followed by a tab, or vice-versa.
To merely conclude that there are one or more inconsistencies within the file is not very helpful in dealing with the problem. Instead you might consider giving the line number of the first line that begins with a space or tab, then giving the line numbers of all subsequent lines that begin with a space or tab that does not match the first line found with such. You could do that as follows (sorry, untested).
def check_file(fname)
file = File.open(fname,"r")
line_no = 0
until file.eof?
first_white = file.gets[/(^\s)/,1]
break if first_white
line_no += 1
end
unless file.eof?
puts "Line #{line_no} begins with a #{(first_white=='\t') ? "tab":"space"}"
until file.eof?
preface = file.gets[/(^\s)/,1))]
puts "Line #{line_no} begins with a #{(preface=='\t') ? "tab":"space"}" \
if preface && preface != first_white
line_no += 1
end
end
file.close
end

reading text file lines in ruby

I would like to scan each line in a text file, EXCEPT the first line.
I would usually do:
while line = file.gets do
...
...etc
end
but line = file.gets reads EVERY single line starting from the first.
How do I read from the second line onwards?
Why not simply call file.gets once and discard the result:
file.gets
while line = file.gets
# code here
end
I would do it in a simple fashion:
IO.readlines('filename').drop(1).each do |line| # drop the first array element
# do any proc here
end
Do you actually want to avoid reading the first line or avoid doing something with it. If you are OK reading the line but you want to avoid processing it then you can use lineno to ignore the line during processing as follows
f = File.new "/tmp/xx"
while line = f.gets do
puts line unless f.lineno == 1
end

Ruby: line by line match range

Is there a way to do the following Perl structure in Ruby?
while( my $line = $file->each_line() ) {
if($line =~ /first_line/ .. /end_line_I_care_about/) {
do_something;
# this will do something on a line per line basis on the range of the match
}
}
In ruby that would read something like:
file.each_line do |line|
if line.match(/first_line/) .. line.match(/end_line_I_care_about/)
do_something;
# this will only do it based on the first match not the range.
end
end
Reading the whole file into memory is not an option and I don't know how big is the chunk of the range.
EDIT:
Thanks for the answers, the answers I got where basically the same as the code I had in the first place. The problem I was having was " It can test the right operand and become false on the same evaluation it became true (as in awk), but it still returns true once."
"If you don't want it to test the right operand until the next evaluation, as in sed, just use three dots ("...") instead of two. In all other regards, "..." behaves just like ".." does."
I am marking the correct answer as the one that pointed me to see that '..' can be turn off in the same call it is made.
For reference the code I am using is:
file.each_line do |line|
if line.match(/first_line/) ... line.match(/end_line_I_care_about/)
do_something;
end
end
Yes, Ruby supports flip-flops:
str = "aaa
ON
bbb
OFF
cccc
ON
ddd
OFF
eee"
str.each_line do |line|
puts line if line =~ /ON/..line =~ /OFF/
#puts line if line.match(/ON/)..line.match(/OFF/) #works too
end
Output:
ON
bbb
OFF
ON
ddd
OFF
I'm not perfectly clear on the exact semantics of the Perl code, assuming you want exactly the same. Ruby does have something that looks and works similarly, or perhaps identically: a Range as a condition works as a toggle. The code you presented works exactly as I imagine you intend.
There are a few caveats, however:
Even after you reach the end condition, lines will keep being read until you reach the end of the file. This may be a performance consideration if you expect the end condition to be near the beginning of a large file.
The start condition can be triggered multiple times, flipping the "switch" back on, doing your do_something and testing for the end condition again. This may be fine if your condition is specific enough, or if you want that behavior, but it's something to be aware of.
The end condition can be called at the same time the start condition is called giving you true for just one line.
Here's an alternative:
started = false
file.each_line do |line|
started = true if line =~ /first_line_condition/
next unless started
do_something()
break if line =~ /last_line_condition/
end
That code reads each line of the file until the start condition is reached. Then it does whatever processing you like starting with that line until you reach a line that matches your end condition, at which point it breaks out of the loop, reading no more lines from the file.
This solution is the closest to your needs. It almost looks like Perl, but this valid Ruby (although the flip-flop operator is kind of discouraged).
The file is read line by line, it is not fully loaded in memory.
File.open("my_file.txt", "r").each_line do |line|
if (line =~ /first_line/) .. (line =~ /end_line_I_care_about/)
do_something
end
end
The parentheses are optional, but they improve readability.

Resources