4

Is it possible to use a pipeline command as an argument to find's -exec option? This means, I want to do something like this:

find . -name CMakeLists* -exec cat '{}' | grep lib \;

where I am trying to execute cat '{}' | grep lib for each file, but this doesn't work. Neither does quoting work. Does anyone have any advice?

Update:

The particular question was answered. Now, is there a way for a generic find <path> -type f -name <name> -exec <pipeline-command> pattern to work?

8
find . -type f -name "CMakeLists*" -exec grep lib /dev/null {} +

This finds files in the current directory whose basename begins or is the string CMakeLists. The argument is escaped (double quoted) so that the shell doesn't expand it before find runs.

There is no need to add cat with a pipe to grep --- it's a useless process with useless IO, here. Adding /dev/null insures that grep will report the filename along with the matching line(s) when there is more than one file to match.

By using {} + as the terminating sequence to the -exec argument, multiple filenames are passed to each invocation of the grep command. Had we used {} \; then a grep process would have been spawned for every file found. Needless process instantiation is expensive if done hundreds or thousands of times.

To use a pipe with a find -exec argument, you need to invoke the shell. A contrived example could be to grep for the string "one" but only if the string "two" isn't present too. This could be done as:

find . -type f -name "CMakeLists*" -exec sh -c 'grep one "$@"|grep -v two' sh {} +

This is predicated on the comments, below, by @muru, @Serg and @Scott, with thanks.

  • 1
    Thanks, but I thinkI think you missed a closing quote. – FloHe Jan 9 '17 at 22:57
  • @FloHe No, this is the opening quote that is missing => -name "CMakeLists*". Never forget the quote, else you may have an unexpected result if there is a file whose name begins with CMakeLists in your current directory. – xhienne Jan 9 '17 at 23:03
  • 4
    IIRC (not sure, though), multiple uses of {} in the -exec command may cause problems in some systems. Use sh -c 'grep one "$0" | grep -v two' {} \; instead – muru Jan 10 '17 at 1:43
  • 1
    Muru is correct, single argument is preferred. Multiple arguments can result in unspecified behavior. Please see: stackoverflow.com/a/12965441/3701431 – Sergiy Kolodyazhnyy Jan 10 '17 at 5:12
  • 2
    In addition to @muru’s comment, (1) not only should you not use {} more than once in your command, but you should not use it as part of a command, as in -exec sh -c 'grep one {} | grep -v two' (1a) because it might not work, and (1b) because it can give rise to code injection vulnerabilities.    (2) It may be even better to do -exec sh -c 'grep one "$1" | grep -v two' sh {} \;.  (2a) It’s better to have $0=sh in case the shell issues error messages.  (2b) This scales more readily to -exec sh -c 'grep one "$@" | grep -v two' sh {} +. – Scott Jan 10 '17 at 6:36
2

Firstly, that's not how the find command terminates.

Secondly, you can just replace cat with grep lib.

find . -name CMakeLists* -exec grep lib '{}' \;

Which basically means "execute grep lib" on each of the search results. You can even omit the ticks around {}. Should work.

  • Ok, thanks: 'find . -name CMakeLists* -exec grep lib '{}' \;' worked. But the general question remains: Is it possible to use the exec option with a piped command – FloHe Jan 9 '17 at 22:49
  • Yes, but this is not the case here. -exec is a flag of the find command. It just calls on an external program to be executed on the results of the FIND command one by one(files by file). You can pipe the results of whatever processing you made to your search results into wc or sort or append the results of your processing to a file with >>. If you want to execute some other command on the search results that have already been processed with some other program called by -exec on a "per file" basis, you should look into xargs. Can you please clarify what you're trying to do here? – Max Jan 9 '17 at 22:57
  • The thing is I want to find all files named CMakeLists in the current folder and all its subdirectories, then printing all the contents of the different files with a leading #/path/tothe/file/CMakelist (before the content) to stdout and finally redirecting the complete output via >> into a new file. Is this possible with a find -exec <pipe-command> pattern? – FloHe Jan 9 '17 at 23:03
  • @FloHe This is not possible the way you have done it in your original question. The answers here by Max and JRFerguson are good. If you are not statisfied because they do not deal with the pipe (because there is no need for it to do a grep), then rephrase your question. Not here in the comments, I mean the original question at the top of the page. – xhienne Jan 9 '17 at 23:13
  • @FloHe If you want the grep results preceded with the full path of the file, see JRFerguson's answer, or use GNU grep's -H flag with this answer. – xhienne Jan 9 '17 at 23:17

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.