Find: multiple `-exec`s with conditions

findxargs

I am trying to include the standard C package stdint.h to files which have the word LARGE_INTEGER, as a part of conversion from Windows to Linux drivers as discussed here for datatypes.
I know the previous threads about find and xargs, here and here.

Code where GNU find part is based mainly on this thread:

gfind /tmp/ -type f                                                      \
    \( -name "*.h" -o -name "*.cpp" \)                                   \
    -exec ggrep -Pl "LARGE_INTEGER" {} +

and its pseudocode extension where I want also that the files must contain the word LARGE_INTEGER

gfind /tmp/ -type f                                                \
    \( -name "*.h" -o -name "*.cpp" \)                             \
    -and -exec ggrep -Pl "LARGE_INTEGER" {} \;                     \
    | xargs gsed -i '1s/^/#include <stdint.h>\n/'

where I am uncertain about -and and giving

gsed: can't read /tmp/: No such file or directory
...

I followed examples in commandlinefu here.

How can you combine a new command to the find based on GNU SED?

Best Answer

I'd use1 find with two -exec actions e.g.:

find . -type f -exec grep -qF SOME_STRING {} \; -exec sed 'COMMAND' {} \;

The second command will run only if the first one evaluates to true i.e. exit code 0 so sed will process the file in question only if the file contains SOME_STRING. It's easy to see how it works:

find . -type f -exec grep -qF SOME_STRING {} \; -print

it should list only those files that contain SOME_STRING. Sure, you can always chain more than two expressions and also use operators like ! (negation) e.g.:

find . -type f -exec grep -qF THIS {} \; ! -exec grep -qF THAT {} \; -print

will list only those files that contain THIS but don't contain THAT.
Anyway, in your case:

gfind /tmp/ -type f \( -name "*.h" -o -name "*.cpp" \) \
-exec ggrep -qF LARGE_INTEGER {} \; \
-exec gsed -i '1s/^/#include <stdint.h>\n/' {} \;

1
I assume your xargs doesn't support -0 or --null option. If it does, use the following construct:

find . -type f -exec grep -lFZ SOME_STRING {} + | xargs -0 gsed -s -i 'COMMAND'

i.e. in your case:

gfind /tmp/ -type f \( -name "*.h" -o -name "*.cpp" \) \
-exec ggrep -lFZ LARGE_INTEGER {} + | \
xargs -0 gsed -s -i '1s/^/#include <stdint.h>\n/'

It should be more efficient than the first one.
Also, both will work with all kind of file names. Note that I'm using grep with -F (fixed string) as it is faster so remove it if you're planning to use a regex instead.

Related Question