4

What is valid syntax to pass combined commands to find with the -exec option?


Example:

find . -type f -name "*.txt" -exec grep FirstKeyWord {} | grep SecondKeyWord \;

fails with the following error message:

find: grep: missing argument to `-exec`
;: No such file or directory

There is the same kind of failure when commands are combined using && or ; instead of |. I even tried quoting the combined commands with ' or with $() or {} or such, but it (miserably) failed.

Note: I'm looking for a one-liner solution, without using custom scripts.

6

You can use a complex shell command in the argument to exec by explicitly invoking a shell there.

find . -type f -name "*.txt" -exec sh -c 'grep FirstKeyWord "$1" | grep SecondKeyWord' -- {} \;

This executes sh with the provided command list for each file, passing the filename in $1.

EDIT: The OP points out that this works and is simpler:

find . -type f -name "*.txt" -exec sh -c 'grep FirstKeyWord {} | grep SecondKeyWord' \;
2

For your specific example, you can do this:

find . -type f -name "*.txt" -exec grep FirstKeyWord {} \; | grep "SecondKeyWord"

BTW, this can also be done without using a second grep. Instead, use egrep with the two alternatives of FirstKeyWord followed by SecondKeyWord or SecondKeyWord followed by FirstKeyWord:

find . -type f -name "*.txt" -exec egrep "FirstKeyWord.*SecondKeyWord|SecondKeyWord.*FirstKeyWord" {} \;

If you can make some guarantees about the resulting pathnames from the find (like no leading or internal spaces and no newlines in the pathnames), you can also do it this way:

find . -type f -name "*.txt" -print | while read filename; do grep "FirstKeyWord" "${filename}" | grep "SecondKeyWord"; done

The more general answer is "you can't" directly from find. Piping commands or using && or || is a function of the shell, not a function of executing processes.

I have used this pattern before, but it is fragile and more complex:

find . -type f -name "*.txt" -print \
    | sed -e 's;[\\$"`!];\\&;g' \
          -e 's;^.*$;grep "FirstKeyWord" "&" | grep "SecondKeyWord";' \
    | sh

Print out each filename, use sed to transform it into a shell script on the fly (being careful to escape the characters in the filename that might cause problems when enclosed in shell double-quotes), then run the result through the shell.

The other solution is to create a shell script that does your processing and call that shell script from find:

$ cat grepit.sh
#!/bin/sh
grep "FirstKeyWord" "$1" | grep "SecondKeyWord"
$ find . -type f -name "*.txt" -exec sh grepit.sh {} \;
  • In your example answer, if the file name contains "SecondKeyWord" but not the contents of the file, the line will be kept whereas it should be filtered out. – Bludzee Sep 9 '16 at 14:01
  • I really need the second command to be combined to the first command so that they become one and single -exec parameter. – Bludzee Sep 9 '16 at 14:01
  • This is only a workaround for the specific example. I'd like to know how to pass complex (=combined) commands to find -exec, not just to use the example from the question. – Bludzee Sep 9 '16 at 14:12
  • Edited to answer the general question: You can't; not directly from find. – J Earls Sep 9 '16 at 14:19
0

The use of "-exec" is highly discouraged, because it creates a lot of sub-process.

Please consider using "find" with "xargs", (using "-print0" and "-0")

find . -type f -name "*.txt" -print0 | xargs -0 grep FirstKeyWord | grep SecondKeyWord

With this, "grep" is spawned once for many files (up to the limit of the number of arguments a program can have).

  • Using xargs is a good idea for the example, but the question is about find with -exec. BTW, can you share a reference website where it is explained why using "-exec" would be "highly discouraged" (maybe an exaggeration)? – Bludzee Sep 12 '16 at 12:28
  • Just test the difference by yourself. I have 407 shell scripts in several directories. 17 seconds for : find '.sh' -exec grep '^#' '{}' ';' ### but 3 seconds for find '.sh' -print0 | xargs -0 grep '#^' – Vouze Sep 13 '16 at 9:22
  • 2
    Exec with + instead of \; to handle multiple parameters performs similarly to xargs; there's no need to conflate the use of exec in the general case with a proliferation of sub-processes. – user115145 Sep 17 '17 at 18:43

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.