4

Is there a way for the find command to search for the first match or occurrence of a string or pattern within each of multiple files? I've been using the usual syntax:

 find dir -iname '*.ext' -exec command 'pattern' {} \;

(I also happen to be searching pdfs with -exec pdfgrep but suppose that would be a special case of the general problem and may be dealt with afterwards or separately.)

Keep in mind this is not same as the commonly-asked problem of producing the first result from a search using find with -quit or head -n 1.

3 Answers 3

8

Just use the -m option of GNU grep which stops reading the file after (in the example) one match.

find dir -iname '*.ext' -exec grep -m 1  'pattern' {} \;
2
  • I'd like to search the first match for each of the files rather than quit after the first file. Commented Jul 31, 2013 at 21:49
  • This command should do just that. It will continue searching each file regardless of any match of any file that find examines.
    – suspectus
    Commented Jul 31, 2013 at 22:09
2

You could do it with an awk script:

find dir -iname '*.ext' -exec awk '/pattern/{print;exit}' {} \;
3
  • I've tried -quit with -exec (pdf)grep but that only exits after the first result; I'd like to search multiple files for first matches. Commented Jul 31, 2013 at 21:52
  • Why are you using -quit? That tells find to exit after the first match.
    – Kevin
    Commented Jul 31, 2013 at 21:54
  • I'm still getting the hang of grep and sed, much less for awk (which I hadn't considered.) Your solution does indeed work (now just to append the filenames that contain the matches, sort of pdfgrep style.) Commented Jul 31, 2013 at 22:08
2

If the searching command has no way to stop after the first match, you can filter its results and keep only the first output line: command 'pattern' /path/to/file | head -n 1. The command will receive a SIGPIPE signal when head exits, so it might go on looking for a few more matches due to buffering but it will stop before the end of the file if there are a lot of matches.

Since you need to run a shell command (to set up the pipe), you need to invoke sh from find. Mind the quoting: you need one layer of quotes for the outer shell, and another for the shell started by find. You can put single quotes around the inner shell command and work in single quotes with the '\'' hack (end single quote literal, \' for a literal single quote, and start a new single quote literal in the same breath), this way you don't need any different quoting in the pattern (unless the pattern contains a ' that you represented as '\'', in which case you'll need to make that '\'\\\'\'').

find dir -iname '*.ext' -exec sh -c 'command '\''pattern'\'' "$0" | head -n 1' {} \;

Instead of worrying about quoting the pattern, you can put it outside and pass it as a parameter.

find dir -iname '*.ext' -exec sh -c 'command "$0" "$1" | head -n 1' 'pattern' {} \;

It will be slightly faster to invoke only one shell and loop over the files.

find dir -iname '*.ext' -exec sh -c '
    for f; do command "$0" "$f" | head -n 1; done
' 'pattern' {} +

You must log in to answer this question.

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