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.

7

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' {} \;
  • I'd like to search the first match for each of the files rather than quit after the first file. – gaberlunzie Jul 31 '13 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 Jul 31 '13 at 22:09
2

You could do it with an awk script:

find dir -iname '*.ext' -exec awk '/pattern/{print;exit}' {} \;
  • 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. – gaberlunzie Jul 31 '13 at 21:52
  • Why are you using -quit? That tells find to exit after the first match. – Kevin Jul 31 '13 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.) – gaberlunzie Jul 31 '13 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' {} +

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.