The find command is a powerful utility in Unix-based systems for searching files in a directory hierarchy. However, when working in a Git repository, it often returns many files that should be ignored due to the .gitignore file.

Wouldn’t it be great if we could extend the find command to exclude the paths specified in .gitignore? With this ifind.sh shell script, we can!

Part of the common-utils utilities, this script enhances find‘s capabilities while also ignoring “noisy” directories that we usually want to exclude from the search.

How Does this Work?

The script uses find‘s ability to exclude paths, by negating (-not) the path-matching operator (-path), builds the command parsing an optional .gitignore and executes it at the end.

Unfortunately, unlike tar there is no simple --exclude option in find to make it parse directly a file with a list of exclusion patterns, so we have to build it path-by-path.

Specifying the Search Directory

First, the script sets the search directory. If no argument is supplied when the script is invoked, the current directory (.) is used by default.

declare -r search_dir=${1:-.}

Constructing the Initial Find Command

The initial find command string is built with the search directory and -type f. The -type f option tells find to look for regular files only.

cmd="find ${search_dir} -type f"

Excluding Noisy Directories

The script then excludes common “noisy” directories such as .git, .run, .idea, .cache. These directories are often excluded from search results as they don’t contain useful code files.

patterns=('.git' '.run' '.idea' '.cache')
for p in "${patterns[@]}"; do
  cmd+=" -not -path ${search_dir}/$p/'*'"
done

Reading from.gitignore

The script then looks for a .gitignore file in the current directory or the search directory. If such a file exists, the script reads each line from the file.

gitignore=$((test -f ./.gitignore && echo './.gitignore') || \
    (test -f "${search_dir}/.gitignore" && echo "${search_dir}/.gitignore"))

Incorporating .gitignore Exclusion Patterns

This script only includes directory exclusion patterns from the .gitignore file when constructing the find command, skipping exclusions themselves (lines starting with !) and comments.

File-specific or non-directory exclusion patterns are ignored:

if [[ -f ${gitignore} ]]; then
  while read -r pattern; do
    if [[ -n "$pattern" && ! ( "$pattern" =~ '^#' || "$pattern" =~ '^!' ) \
          && "$pattern" =~ '/$' ]]; then
      cmd+=" -not -path '$search_dir/$pattern*'"
    fi
  done < ${gitignore}
fi

Executing the Find Command

After the find command is fully constructed, the script finally executes it.

eval "$cmd"

Example usage

This works both in a directory which contains .gitignore:

$ cd common-utils
~/Development/common-utils

$ ifind
./images/common-utils-small.jpeg
./Makefile
./install.sh
...
./head.html
./templates/Makefile-template
./templates/go-make-version-tag.sh
./templates/commons.cmake

compare with the output of simply running find .

and also from another directory, which does not contain the .gitignore (worth noting that if the directory contains one that one will be used):

$ cd Documents
~/Documents

$ ifind ~/Development/common-utils
/Users/marco/Development/common-utils/images/common-utils-small.jpeg
/Users/marco/Development/common-utils/Makefile
/Users/marco/Development/common-utils/install.sh
...
/Users/marco/Development/common-utils/manifest.json
/Users/marco/Development/common-utils/head.html
/Users/marco/Development/common-utils/templates/Makefile-template
/Users/marco/Development/common-utils/templates/go-make-version-tag.sh
/Users/marco/Development/common-utils/templates/commons.cmake

Conclusions

This ifind.sh script makes the find command more intelligent by excluding irrelevant paths specified in the .gitignore file, helping you efficiently find the files you need, amid a sea of project files.

This is available by installing common-utils Release 0.10.5 and later.

Happy finding!

Leave a comment

Trending