Stopping grep after one match in a for loop

(apologies in advance if this one is incredibly obvious to anyone out there, i've been :wall: at this one for some time now)

What I've got is a newline-separated list of filenames to search the contents of around 2,000 XML files for, and return the name of that file. This seems like a job for a "for" loop, so I enter this on the command line:

for i in `cat ~/targets.txt`; do grep -l -m 1 $i *.xml; done

The problem here is that one of my target filenames might appear in more than one file - I only want grep to return the very first match in the very first file that it finds, and then stop and move on to the next iteration of the loop. (Which is what I was attempting to do with -m 1)

Any ideas here?

That's a useless use of backticks and useless use of cat, not just inefficient but potentially harmful -- if the file is too large it may silently return less data than is really in the file.

This construct has none of those limits:

while read LINE
do
       ...
done < filename

Since you have -m 1, you have GNU grep, which supports -F and -f, so a loop isn't actually necessary:

grep -m 1 -f targets.txt *.xml

If targets.txt contains fixed strings and not regexes, add -F before -f.

---------- Post updated at 02:30 PM ---------- Previous update was at 02:29 PM ----------

...and if all you care about is the filename and not what precisely is matching, -l will work instead of -m 1.

2 Likes
while IFS= read -r; do
  grep -Fl "$REPLY" *.xml |
    head -n 1 
done < ~/targets.txt 

Edit: Yep, Corona688 is right :slight_smile: (-m1 removed)

1 Like

Huh! I didn't know about those gotchas with cat and the backticks (they call out exactly what I was doing there as very very wrong), so thanks for that :slight_smile:

Unfortunately this didn't solve my problem though. It's still returning more than one file for each line in my targets.txt

grep -l -Ff targets.txt *.xml

Radoulov's script did though! I never would have thought about using a while loop in that way. It seems I have much to learn still. Thanks!!

#!/usr/bin/ksh
while read mString; do
  echo "Now searching for <${mString}>"
  for mFName in *.xml; do
    echo "Now searching on file <$mFName>"
    mCnt=$(grep -c ${mString} ${mFName})
    if [[ ${mCnt} -ne 0 ]]; then
      echo "Found <${mString}> on file <${mFName}>"
      break
    fi
  done
done < Strings_File

I got it exactly backwards, I think :wall: It doesn't return more than one match per file, but might return more than one file per match!

You do have to use a loop for that, which other people obviously have covered already.