Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

Is there a way to logically combine two shell commands that are invoked with find - exec?

For instance to print out all the .csv files that contain the string foo together with its occurrence I would like to do:

find . -iname \*.csv -exec grep foo {} && echo {} \;

but bash complains with "missing argument to '-exec' "

share|improve this question
2  
You could use 2 -exec in sequence or use a single -exec sh -c 'grep foo "$0" && printf %s\\n "$0"' {} \;. –  jw013 Nov 13 '12 at 19:03
    
This has tripped me up repeatedly: I always expect that the first argument passed to sh (in this case {}) will be $1 and $0 will be something like sh. But in fact, you are correct, the first argument shows up as $0. Having the first argument be the name of the invoking command is just a convention, that isn't automatically enforced in these cases. –  dubiousjim Nov 13 '12 at 19:38
    
Maybe should be merged with this question: unix.stackexchange.com/q/18077/4801 –  dubiousjim Nov 14 '12 at 16:02
add comment

3 Answers

up vote 5 down vote accepted

-exec is a predicate that runs a command (not a shell) and evaluates to true or false based on the outcome of the command (zero or non-zero exit status).

So:

find . -iname '*.csv' -exec grep foo {} \; -print

would print the file path if grep finds foo in the file. Instead of -print you can use another -exec predicate or any other predicate

find . -iname '*.csv' -exec grep foo {} \; -exec echo {} \;

See also the ! and -o find operators for negation and or.

Alternatively, you can start a shell as:

find . -iname '*.csv' -exec sh -c '
   grep foo "$1" && echo "$1"' sh {} \;

Or to avoid having to start a shell for every file:

find . -iname '*.csv' -exec sh -c '
  for i do
    grep foo "$i" && echo "$i"
  done' sh {} +
share|improve this answer
add comment

The problem you're facing is that the shell first parses the command line, and sees two simple commands separated by the && operator: find . -iname \*.csv -exec grep foo {}, and echo {} \;. Quoting && (find . -iname \*.csv -exec grep foo {} '&&' echo {} \;) bypasses that, but now the command executed by find is something like grep with the arguments foo, wibble.csv, &&, echo and wibble.csv. You need to instruct find to run a shell that will interpret the && operator:

find . -iname \*.csv -exec sh -c 'grep foo "$0" && echo "$0"' {} \;

Note that the first argument after sh -c SOMECOMMAND is $0, not $1.

You can save the startup time of a shell process for every file by grouping the command invocations with -exec … +. For ease of processing, pass some dummy value as $0 so that "$@" enumerates the file names.

find . -iname \*.csv -exec sh -c 'for x in "$@"; do grep foo "$x" && echo "$x"; done' \ {} +

If the shell command is just two programs separated by &&, find can do the job by itself: write two consecutive -exec actions, and the second one will only be executed if the first one exits with the status 0.

find . -iname \*.csv -exec grep foo {} \; -exec echo {} \;

(I assume that grep and echo are just for illustration purpose, as -exec echo can be replaced by -print and the resulting output is not particularly useful anyway.)

share|improve this answer
1  
I tend to avoid using "$0" for that, as it's also used by the shell to display error messages. For instance, you could see a confusing ./some-file: grep: command not found error message. -exec find sh -c '... "$1"' sh {} \; wouldn't have the issue. There's a typo (related) in your second find command. –  Stéphane Chazelas Nov 14 '12 at 9:31
add comment

In this specific case I would do:

find . -iname \*.csv -exec grep -l foo \{\} \;

Or if you have ack:

ack -al -G '.*\.csv' foo

To answer your actual question, something like this may work:

find . -iname \*.csv -exec sh -c "grep foo {} && echo {}" \;

share|improve this answer
3  
That latter find command is not-only non-portable but also very dangerous as file paths end up being evaluated as shell code. –  Stéphane Chazelas Nov 13 '12 at 23:16
    
All the more reason to try and avoid it by using ack/grep properly :) –  Dennis Kaarsemaker Nov 13 '12 at 23:48
1  
jw013's alternative (given in a comment to the question) is safer in this respect. (Just noticed that Gilles' and Stephane's answers use the same technique.) –  dubiousjim Nov 14 '12 at 2:22
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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