Search code examples
bashfindconditional-statementsgnu

unix find in script which needs to met a combination of condiations and execute something for all found files


Let's assume we have the following files and directories:

-rw-rw-r--.   a.c
-rw-rw-r--.   a.cpp
-rw-rw-r--.   a.h
-rw-rw-r--.   a.html
drwxrwxr-x.   dir.c
drwxrwxr-x.   dir.cpp
drwxrwxr-x.   dir.h

I want to execute grep on all the files (it should also look in subdirectories) which meet the following conditions:

  • Only files, not directories.
  • Only files which end with .h, .c and .cpp.

On files found, execute grep and print the file name where grep finds something.

But this ends up in a very long command with a lot of repetitions like:

 find . -name '*.c' -type f -exec grep hallo {} \; -print -o -name '*.cpp' -type f -exec grep hallo {} \; -print -o -name '*.h' -type f -exec grep hallo {} \; -print

Can the conditions be grouped to remove the repetitions or are there any other possible simplifications?

My system is Fedora 33, with GNU grep, GNU find and bash available.


Solution

    1. With Bash's extglob and globstar special options:

      shopt +s extglob globstar
      grep -s regex **/*.+(cpp|c|h)
      

      You can put the first line in ~/.bashrc so you don't need to manually enable them options in every shell. If you happened to have directories with those extensions, Grep would complain without the -s flag.

    2. In GNU Find, use the -regex option to make it easier:

      find . -type f -regex '.*\.\(c\|h\|cpp\)' -exec grep regex {} +
      find . -type f -regextype awk -regex '.*\.(c|h|cpp)' -exec grep regex {} +
      

      More on Find's regextypes.

    3. With POSIX tools only:

      find . -type f \( -name '*.[ch]' -o -name '*.cpp' \) -exec grep regex {} +