tags:

views:

46

answers:

3

I'm creating some basic work assistance utilities using Ruby. I've hit a problem that I don't really need to solve, but curiosity has the best of me.

What I would like to be able to do is search the contents of a file, starting from a particular line and find the first PREVIOUS occurrence of a string.

For example, if I have the following text saved in a file, I would like to be able to search for "CREATE PROCEDURE" starting at line 4 and have this return/output "CREATE PROCEDURE sp_MERGE_TABLE"

CREATE PROCEDURE sp_MERGE_TABLE
AS
 SOME HORRIBLE STATEMENT
 HERE

CREATE PROCEDURE sp_SOMETHING_ELSE
AS
 A DIFFERENT STATEMENT
 HERE

Searching for content isn't a challenge, but specifying a starting line - no idea. And then searching backwards... well...

Any help at all appreciated!

TIA!

+1  A: 

I think you have to read file line one by line

then follwing will work

  flag=true
  if flag && line.include?("CREATE PROCEDURE")
    puts line
    flag=false
  end 
Salil
I appreciate the attempt and I thought about doing this. But I would actually have to read the file line by line backwards from a specified starting line. That would mean loading the whole thing into an array and then walking backwards. Do-able, but ugly. As mentioned above, I'm really just trying to learn some more elegant file manipulation methods through this.Thanks for the try!
Bobby B
+1  A: 

If performance isn't a big issue, you could just use a simple loop:

# pseudocode
line_no = 0
while line_no < start_line
  read line from file
  if content_found in this line
    last_seen = line_no # or file offset
  end
  line_no += 1
end
return last_seen

I'm afraid you will have to work line by line through the file, unless you have some index over it, pointing to the beginnings of the lines. That would make the loop a little bit simpler but working through the file in backwards manner is harder (unless you keep the whole file in memory).

szeryf
A: 

Edit:

I just had a much better idea, but I'm going to include the old solution anyway.

The benefit of searching backwards means you only have to read the first chunk of the file, upto the specified line number. For proximity, you get closer and closer to the start_line, and if you find a match you just forget the old one.. You still read in some redundant data at the beginning, but at least it's O(n)

path = "path/to/file"
start_line = 20
search_string = "findme!"

#assuming file is at least start_line lines long
match_index = nil
f = File.new(path)
start_line.times do |i|
   line = f.readline
   match_index = i if line.include? search_string
end

puts "Matched #{search_string} on line #{match_index}"

Of course, bear in mind that the size of this file plays an important role in answering your question.

If you wanted to get really serious, you could look into the IO class - it seems like this might be the ultimate solution. Untested, just a thought.

f = File.new(path)
start_line.downto(0) do |i|
  f.lineno = i
  break if f.gets.include?(search_string)
end

Original:

For an exhaustive solution, you could try something like the following. The downside is you'd need to read the whole file into memory, but it takes into account continuing from the bottom-up if it gets to the top without a match. Untested.

path = "path/to/file"
start_line = 20
search_string = "findme!"

#get lines of the file into an array (chomp optional)
lines = File.readlines(path).map(&:chomp)

#"cut" the deck, as with playing cards, so start_line is first in the array
lines = lines.slice!(start_line..lines.length) + lines

#searching backwards can just be searching a reversed array forwards
lines.reverse!

#search through the reversed-array, for the first occurence
reverse_occurence = nil
lines.each_with_index do |line,index|
  if line.include?(search_string)
    reverse_occurence = index
    break
  end
end

#reverse_occurence is now either "nil" for no match, or a reversed-index
#also un-cut the array when calculating the index
if reverse_occurence
   occurence = lines.size - reverse_occurence - 1 + start_line
   line = lines[reverse_occurence]
   puts "Matched #{search_string} on line #{occurence}"
   puts line
end
Jeriko
BAM!!! I love it! Your original solution is where my mind first went - your new and improved solution is where I wanted my mind to go. It looks like there isn't any type of file/string find call that can be modified to meet my requirements. This answer is the next best thing as far as I'm concerned.Thanks a ton for all the help!
Bobby B
Updated the answer again - seems you can do things like `File.lineno = my_line_number` and so on. Also `seek` and `read` might be your friend, but it's probably overkill unless your files are ginormous ;)
Jeriko