3

I have a file (myfile) organized in paragraphs, i.e. with empty lines separating the entries. I want to retrieve some of these paragraphs according to match.

Now, it's all fun and games when the match is only one: I simply do awk -v RS='' '/match/ {print}' myfile, as in here. The thing is that I have hundreds of matches to find in file, which I collected in another file (matchfile). If I had to retrieve only the matching line, I'd do a grep -f matchfile myfile.

Is there a way to do something similar to grep -f retrieving the whole paragraph? My Unix flavour doesn't support grep -p.

0

3 Answers 3

1

You could turn paragraphs into single lines, use grep -f matchfile on the result and then restore the newlines:

sed '/^$/s/^/\x02/' myfile | tr \\n$'\002' $'\003'\\n \
| grep -f matchfile |  tr $'\003' \\n | head -n -1

You can do without the head if the trailing empty line in the output doesn't bother you.
So... sed adds \x02 to the beginning of each empty line then tr translates all newlines to \x03 and all \x02 to newlines (effectively turning all paragraphs into single lines where the original lines are fields separated by some low ascii char that's unlikely to occur in your text file - in this case \x03) then grep selects only the matching "lines"; finally, the second tr restores the newlines and head discards the trailing empty line (you could use any other tool e.g. sed \$d).
Really, the easiest way to understand how this works is to run it in steps: run only the 1st command, then the 1st & 2nd and so on... and observe the output - it should be self-explanatory1.


1: if you got familiar with tr after reading the manual...

2
  • 1
    I'm not a perl guy but I'm sure perl is more suited for this job... Nov 22, 2016 at 15:53
  • I like your solution, it's the style I normally use to solve problems. Could you explain what you did here? I'm not quite familiar with tr. Nov 24, 2016 at 16:05
1

Come on, don't give up on awk so fast !

awk 'NR == FNR {
          aMatch[NR]=$0
          n=FNR
          next;
    }
    {
          RS="\n( |\t)*\n"
          for(i=1; i<n+1; i++) {
             if($0 ~ aMatch[i]) {
               print
               printf "\n"
               break                   
             }                 
          }
    }' matchFile myFile | head -n-1

You might want to put that into a script though :

awk -f myscript.awk matchFile myFile | head -n-1

The solution in awk script form, with annotations on what it does:

# This block's instructions will only be executed for the first file (containing the lines to be matched)
# NR = number of line read, and FNR = number of line read in current file   
# So the predicate NR == FNR is only true when reading the first file !
NR == FNR {
   aMatch[NR]=$0          # Store the line to match in an array
   n=FNR                  # Store the number of matches
   next;                  # Tells awk to skip further instructions (they are intended for the other file) and process the next record
}
# We are now processing the second file (containing the paragraphs)
{
   RS="\n( |\t)*\n"          # Set record separator to a blank line. Instead of a single line, a record is now the entire paragraph
   for(i=1; i<n+1; i++) {    # Loop on all possible matches
      if($0 ~ aMatch[i]) {   # If $0 (the whole record i.e. paragraph) matches a line we read in file 1 then
         print               # Print the record (i.e. current paragraph)
         printf "\n"         # Print a new line to separate them. However we will end up with a trailing newline, hence the pipe to head -n-1 to get rid of it.
         break               # We need to break out of the loop otherwise paragraphs with n matches will be printed n times
      }                      # End of loop on matches
   }                         # End of 2nd file processing
}
2
  • Thanks, this did the job! Can you explain in detail what did you do here, so next time I won't give up on poor awk? Nov 24, 2016 at 15:56
  • Sure thing, I'll edit the post and add comments ! Nov 24, 2016 at 17:02
0

Very simple to do this:

awk -v RS="" -v ORS="\n\n" '/match/' myfile

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .