Ruby: line by line match range

2019-05-30 00:15发布

问题:

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

回答1:

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:

  1. 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.

  2. 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.

  3. 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.



回答2:

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


回答3:

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.