Determine last line in Ruby - ruby

I'm wondering how I can determine when I am on the last line of a file that I reading in. My code looks like
File.open(file_name).each do |line|
if(someway_to_determine_last_line)
end
I noticed that there is a file.eof? method, but how would I call the method as the file is being read? Thanks!

If you're iterating the file with each, then the last line will be passed to the block after the end-of-file is reached, because the last line is, by definition, the line ending with EOF.
So just call file.eof? in the block.
If you'd like to determine if it's the last non-empty line in the file, you'd have to implement some kind of readahead.

Depending on what you need to do with this "last non-empty line", you might be able to do something like this:
last_line = nil
File.open(file_name).each do |line|
last_line = line if(!line.chomp.empty?)
# Do all sorts of other things
end
if(last_line)
# Do things with the last non-empty line.
end

Secret sauce is .to_a
lines = File.open(filename).to_a
Get the first line:
puts lines.first
Get the last line:
puts lines.last
Get the n line of a file:
puts lines.at(5)
Get the count of lines:
puts lines.count

fd.eof? works, but just for fun, here's a generic solution that works with any kind of enumerators (Ruby 1.9):
class Enumerator
def +(other)
Enumerator.new do |yielder|
each { |e| yielder << e }
other.each { |e| yielder << e }
end
end
def with_last
Enumerator.new do |yielder|
(self + [:some_flag_here]).each_cons(2) do |a, b|
yielder << [a, b == :some_flag_here]
end
end
end
end
# a.txt is a file containing "1\n2\n3\n"
open("a.txt").lines.with_last.each do |line, is_last|
p [line, is_last]
end
Which outputs:
["1\n", false]
["2\n", false]
["3\n", true]

Open your file and use the readline method:
To simply manipulate last line of file do the following:
f = File.open('example.txt').readlines
f.each do |readline|
if readline[f.last]
puts "LAST LINE, do something to it"
else
puts "#{readline} "
end
end
Line 1 reads the file in as an array of lines
Line 2 uses that object and iterates over each of them
Line 3 tests if the current line matches the last line
Line 4 acts if it's a match
Line 5 & 6 handle behavior for non-matching circumstance

Related

Passing an gets.chomp as an argument

input_file = ARGV.first
def print_all(f)
puts f.read
end
def rewind(f)
f.seek(0)
end
def print_a_line(line_count, f)
puts "#{line_count}, #{f.gets.chomp}"
end
current_file = open(input_file)
puts "First let's print the whole file:¥n"
print_all(current_file)
puts "Let's rewind kind a like a tape"
rewind(current_file)
puts "Let's print three lines:"
current_line = 1
print_a_line(current_line, current_file)
current_line += 1
print_a_line(current_line, current_file)
I'm sure there is a kinda similar post to this, but my question is a bit different. As seen above, the print_a_line method got two params that are line_count and f.
1) As I understood, line_count argument only serves as a variable which is current_line and it is just an integer. How does it relate to the rewind(f) method because when I run the code, the method print_a_line shows this:
1, Hi
2, I'm a noob
where 1 is the first line and 2 is the second. line_count is just a number, how does ruby know that 1 is line 1 and 2 is line 2?
2) Why use gets.chomp in method print_a_line? If I pass just f like this
def print_a_line(line_count, f)
puts "#{line_count}, #{f}"
end
I'll get a crazy result which is
1, #<File:0x007fccef84c4c0>
2, #<File:0x007fccef84c4c0>
Because IO#gets reads next line from readable I/O stream(in this case it's a file) and returns a String object when reading successfully and not reached end of the file. And, chomp removes carriage return characters from String object.
So when you have a file with content such as:
This is file content.
This file has multiple lines.
The code will print:
1, This is file content.
2, This file has multiple lines.
In the second case, you're passing file object itself and not reading it. Hence you see those objects in output.

Get line number of beginning and end of Ruby method given a ruby file

How can I find the line of the beginning and end of a Ruby method given a ruby file?
Say for example:
1 class Home
2 def initialize(color)
3 #color = color
4 end
5 end
Given the file home.rb and the method name initialize I would like to receive (2,4) which are the beginning and end lines.
Finding the end is tricky. The best way I can think of is to use the parser gem. Basically you'll parse the Ruby code into an AST, then recursively traverse its nodes until you find a node with type :def whose first child is :initialize:
require "parser/current"
def recursive_find(node, &block)
return node if block.call(node)
return nil unless node.respond_to?(:children) && !node.children.empty?
node.children.each do |child_node|
found = recursive_find(child_node, &block)
return found if found
end
nil
end
src = <<END
class Home
def initialize(color)
#color = color
end
end
END
ast = Parser::CurrentRuby.parse(src)
found = recursive_find(ast) do |node|
node.respond_to?(:type) && node.type == :def && node.children[0] == :initialize
end
puts "Start: #{found.loc.first_line}"
puts "End: #{found.loc.last_line}"
# => Start: 2
# End: 4
P.S. I would have recommended the Ripper module from the standard library, but as far as I can tell there's no way to get the end line out of it.
Ruby has a source_location method which gives you the file and the beginning line:
class Home
def initialize(color)
#color = color
end
end
p Home.new(1).method(:initialize).source_location
# => ["test2.rb", 2]
To find the end, perhaps look for the next def or EOF.
Ruby source is nothing but a text file. You can use linux commands to find the method line number
grep -nrw 'def initialize' home.rb | grep -oE '[0-9]+'
I have assumed that the file contains the definition of at most one initialize method (though generalizing the method to search for others would not be difficult) and that the definition of that method contains no syntax errors. The latter assumption is probably required for any method to extract the correct line range.
The only tricky part is finding the line containing end that is the last line of the definition of the initialize method. I've used Kernel#eval to locate that line. Naturally caution must be exercised whenever that method is to be executed, though here eval is merely attempting to compile (not execute) a method.
Code
def get_start_end_offsets(fname)
start = nil
str = ''
File.foreach(fname).with_index do |line, i|
if start.nil?
next unless line.lstrip.start_with?('def initialize')
start = i
str << line.lstrip.insert(4,'_')
else
str << line
if line.strip == "end"
begin
rv = eval(str)
rescue SyntaxError
nil
end
return [start, i] unless rv.nil?
end
end
end
nil
end
Example
Suppose we are searching a file created as follows1.
str = <<-_
class C
def self.feline
"cat"
end
def initialize(arr)
#row_sums = arr.map do |row|
row.reduce do |t,x|
t+x
end
end
end
def speak(sound)
puts sound
end
end
_
FName = 'temp'
File.write(FName, str)
#=> 203
We first search for the line that begins (after stripping leading spaces) "def initialize". That is the line at index 4. The end that completes the definition of that method is at index 10. We therefore expect the method to return [4, 10].
Let's see if that's what we get.
p get_start_end_offsets(FName)
#=> [4, 10]
Explanation
The variable start equals the index of the line beginning def initialize (after removing leading whitespace). start is initially nil and remains nil until the "def initialize" line is found. start is then set to the index of that line.
We now look for a line line such that line.strip #=> "end". This may or may not be the end that terminates the method. To determine if it is we eval a string that contains all lines from the one that begins def initialize to the line equal to end just found. If eval raises a SyntaxError exception that end does not terminate the method. That exception is rescued and nil is returned. eval will return :_initialize (which is truthy) if that end terminates the method. In that case the method returns [start, i], where i is the index of that line. nil is returned if no initialize method is found in the file.
I've converted "initialize" to "_initialize" to suppress the warning (eval):1: warning: redefining Object#initialize may cause infinite loop)
See both answers to this SO question to understand why SyntaxError is being rescued.
Compare indentation
If it is known that "def initialize..." is always indented the same amount as the line "end" that terminates the method definition (and no other lines "end" between the two are indented the same), we can use that fact to obtain the beginning and ending lines. There are many ways to do that; I will use Ruby's somewhat obscure flip-flop operator. This approach will tolerate syntax errors.
def get_start_end_offsets(fname)
indent = -1
lines = File.foreach(fname).with_index.select do |line, i|
cond1 = line.lstrip.start_with?('def initialize')
indent = line.size - line.lstrip.size if cond1
cond2 = line.strip == "end" && line.size - line.lstrip.size == indent
cond1 .. cond2 ? true : false
end
return nil if lines.nil?
lines.map(&:last).minmax
end
get_start_end_offsets(FName)
#=> [4, 10]
1 The file need not contain only code.

ruby koan sandwich line count

I was working through the about_sandwich_code example in the Ruby Koans example, completed code link here about_sandwich_code.rb for reference. The following is the relevant reference code for easy reference.
def count_lines(file_name)
file = open(file_name)
count = 0
while file.gets
count += 1
end
count
ensure
file.close if file
end
def test_counting_lines
assert_equal 4, count_lines("example_file.txt")
end
# ------------------------------------------------------------------
def find_line(file_name)
file = open(file_name)
while line = file.gets
return line if line.match(/e/)
end
ensure
file.close if file
end
def test_finding_lines
assert_equal "test\n", find_line("example_file.txt")
end
def file_sandwich(file_name)
file = open(file_name)
yield(file)
ensure
file.close if file
end
In my attempt at writing find_line2 method I tried the following code which compiled.
def find_line2(file_name)
file_sandwich(file_name) do |file|
while file.gets
return file.gets if file.gets.match(/e/)
end
end
end
def test_finding_lines2
assert_equal "test\n", find_line2("example_file.txt")
end
For reference, example_file.txt.
However, the koans returned the following in the terminal window: Expected "test\n" to equal nil
This raised my awareness as the penultimate koan code/test for the analogous finding lines function pre-sandwich code solved that koan.
def test_finding_lines
assert_equal "test\n", find_line("example_file.txt")
end
As I tried different options, I realized that the following find_line2 implementation
def find_line2(file_name)
file_sandwich(file_name) do |file|
while line = file.gets
return line if line.match(/e/)
end
end
When run with
def test_finding_lines2
assert_equal "test\n", find_line2("example_file.txt")
end
resolves the koan rather than tell me that the test should equal nil as the prior screenshot shows. So what this amounts to, as I understand in this moment is that my first implementation somehow changes what the koan program expects as the check, which sort of baffles me. I think it means my attempt somehow broke the koan, but I'm not sure why. Actually, after running file_sandwich and my first implementation of find_line2 I see the call find_line2("example_file.txt") to return nil, so for some reason file.gets acts differently than using line after the line = file.gets in the while statement.
Can someone explain why my first implementation and the answer don't equal? I believe that the answer lies in a more clear understanding of blocks?
def find_line2(file_name)
file_sandwich(file_name) do |file|
while file.gets #gets the next line in the file
if file.gets.match(/e/) #gets the next line and checks for a match
return file.gets #gets the next line and returns it.
end #restructured for clarity
end
end
end
So with the example file: This\nis\na\ntest you code gets This\n, gets is\n and checks it (fail). On the next iteration, your code gets a\n, gets test\n and checks it (true), then gets the next line (nil) and returns it.

How do I detect end of file in Ruby?

I wrote the following script to read a CSV file:
f = File.open("aFile.csv")
text = f.read
text.each_line do |line|
if (f.eof?)
puts "End of file reached"
else
line_num +=1
if(line_num < 6) then
puts "____SKIPPED LINE____"
next
end
end
arr = line.split(",")
puts "line number = #{line_num}"
end
This code runs fine if I take out the line:
if (f.eof?)
puts "End of file reached"
With this line in I get an exception.
I was wondering how I can detect the end of file in the code above.
Try this short example:
f = File.open(__FILE__)
text = f.read
p f.eof? # -> true
p text.class #-> String
With f.read you read the whole file into text and reach EOF.
(Remark: __FILE__ is the script file itself. You may use you csv-file).
In your code you use text.each_line. This executes each_line for the string text. It has no effect on f.
You could use File#each_line without using a variable text. The test for EOF is not necessary. each_line loops on each line and detects EOF on its own.
f = File.open(__FILE__)
line_num = 0
f.each_line do |line|
line_num +=1
if (line_num < 6)
puts "____SKIPPED LINE____"
next
end
arr = line.split(",")
puts "line number = #{line_num}"
end
f.close
You should close the file after reading it. To use blocks for this is more Ruby-like:
line_num = 0
File.open(__FILE__) do | f|
f.each_line do |line|
line_num +=1
if (line_num < 6)
puts "____SKIPPED LINE____"
next
end
arr = line.split(",")
puts "line number = #{line_num}"
end
end
One general remark: There is a CSV library in Ruby. Normally it is better to use that.
https://www.ruby-forum.com/topic/218093#946117 talks about this.
content = File.read("file.txt")
content = File.readlines("file.txt")
The above 'slurps' the entire file into memory.
File.foreach("file.txt") {|line| content << line}
You can also use IO#each_line. These last two options do not read the entire file into memory. The use of the block makes this automatically close your IO object as well. There are other ways as well, IO and File classes are pretty feature rich!
I refer to IO objects, as File is a subclass of IO. I tend to use IO when I don't really need the added methods from File class for the object.
In this way you don't need to deal with EOF, Ruby will for you.
Sometimes the best handling is not to, when you really don't need to.
Of course, Ruby has a method for this.
Without testing this, it seems you should perform a rescue rather than checking.
http://www.ruby-doc.org/core-2.0/EOFError.html
file = File.open("aFile.csv")
begin
loop do
some_line = file.readline
# some stuff
end
rescue EOFError
# You've reached the end. Handle it.
end

Skipping the first line when reading in a file in 1.9.3

I'm using ruby's File to open and read in a text file inside of a rake
task. Is there a setting where I can specify that I want the first line of
the file skipped?
Here's my code so far:
desc "Import users."
task :import_users => :environment do
File.open("users.txt", "r", '\r').each do |line|
id, name, age, email = line.strip.split(',')
u = User.new(:id => id, :name => name, :age => age, :email => email)
u.save
end
end
I tried line.lineno and also doing File.open("users.txt", "r", '\r').each do |line, index| and next if index == 0 but have not had any luck.
Change each to each_with_index do |line, index| and next if index == 0 will work.
function drop(n) will remove n lines from the beginning:
File.readlines(filename).drop(1).each do |line|
puts line
end
It will read the whole file into an array and remove first n lines. If you are reading whole file anyway it's probably the most elegant solution.
When reading larger files foreach will be more efficient, as it doesn't read all data into memory:
File.foreach(filename).with_index do |line, line_num|
next if line_num == 0
puts line
end
File.open("users.txt", "r", '\r') do |file|
lines = file.lines # an enumerator
lines.next #skips first line
lines.each do |line|
puts line # do work
end
end
Making use of an enumerator, which 'remembers' where it is.
You probably really want to use csv:
CSV.foreach("users.txt", :headers, :header_converters => :symbol, :col_sep => ',') do |row|
User.new(row).save
end
File.readlines('users.txt')[1..-1].join()
Works good too.
if you want to keep the file as IO the whole time (no array conversions) and you plan on using the data in the first line:
f = File.open('users.txt', 'r')
first_line = f.gets
body = f.readlines
More likely though, what you want is handled by CSV or FasterCSV as others have pointed out. My favorite way to handle files with a header line is to do:
FasterCSV.table('users.txt')
Since a few answers (no longer ?) work in Ruby 1.9.3, here a working sample of the three best methods
# this line must be dropped
puts "using drop"
File.readlines(__FILE__).drop(1).each do |line|
puts line
end
puts ""
puts "using a range"
File.readlines(__FILE__)[1..-1].each do |line|
puts line
end
puts ""
puts "using enumerator"
File.readlines(__FILE__).each do |file, w|
lines = file.lines # an enumerator
lines.next #skips first line
lines.each do |line|
puts line
end
end
The OP said lineno didn't work for them, but I'm guessing it wasn't applied in the correct way. There's lots of ways achieve what the OP is asking for, but using lineno might help you shorten up your code without having to use readlines, which is sometimes too memory intensive.
From the 1.9.3 docs
f = File.new("testfile")
f.each {|line| puts "#{f.lineno}: #{line}" }
produces:
1: This is line one
2: This is line two
3: This is line three
4: And so on...
Note that is a method you can call from the file object, but not the object yielded to the block.
2: require 'pry'; binding.pry
=> 3: f.each {|line| puts line.lineno }
[1] pry(#<SomeFile>)> line.lineno
NoMethodError: undefined method `lineno' for #<String:0x00007fa7d682b920>
You can find the same example with identical code in docs for the latest stable version of Ruby today (2.5.1).
So going off the example, the code might look like
f = File.new("testfile")
o = File.open("output.txt", w)
f.each do |line|
next if f.lineno == 1
o << line
end

Resources