bash – can I do : find …. -exec this && that?

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’ “

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 {}" ;

Answered By: Dennis Kaarsemaker

-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 file do
    grep foo "$file" && echo "$file"
  done' sh {} +
Answered By: Stéphane Chazelas

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.)

Categories: Answers Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.