How to find

🗓️
•
🔄
•
⏳ 7 min

It not only helps locate files in the file system, it also allows you to manipulate what it finds.

Keep in mind

Not all find implementations are created equal. This post is best on the GNU implementation.

The basics

The find command has the following structure:

find [DIR] [OPTS] [EXP]

Where DIR is the directory in which you wish to search, OPTS are search options, and EXP is an expression by which to search.

The most basic practical use might look something like this:

sh
find . -name 'config'

Which translates to “find anything named exactly config within cwd (.) and its contained directories”.

This would print the paths (relative to where find is launched) for all files and directories that match the given pattern.

So for a file named config in a directory named config, it would output:

src/config
src/config/config

The options

For clarity, I’ve grouped them under three categories: Filters, Operators and Actions.

This separation should make them easier to reason about.

Filters

Technically called tests, these will tell find what ‘sort of things’ you are after.

-type

Tells find to only consider certain type of files:

-type f -> files
-type d -> directories
-type l -> symlinks

-name / -path

When asking for the name, find will look for a match with the last portion of the path, so after the last /.

When asking for the path, it will look for any path that exactly match the given string.

So if you want to find all files within a something directory, but there are many such directories under cwd, you would tell find to look for files with something as a part of their paths:

sh
find . -type f -path '*something*'

As you can see, the EXP part of the command takes a reduced regex (which is why it only matches the exact string by default).

Here, we include the wild-card *, which will match for cwd/path/something/myFile and/or cwd/something/myOtherFile.

Both the -name and the -path filters have case-insensitive versions: -iname and -ipath.

-regex

Unlock the full potential of regex by using the -regex flag!

-mindepth / -maxdepth

Unless told otherwise, find will always search recursively throughout the directory structure. You can limit the scope of the command by setting its -mindepth and -maxdepth.

These filters take a number as parameter: 1 is the directory passed to find (cwd as . in our examples so far), 2 is its direct children directories, and so on.

So find . -maxdepth 1 -type f -name 'whoami' would look for a file named whoami only within the starting directory (ignoring its child directories).

While find . -mindepth 2 -type f -name 'whoami' would look for that same file in all directories under cwd, excluding cwd itself.

Operators

Mix, match or negate multiple searches:

-not -> negate following pattern
-a -> 'and' following pattern
-o -> 'or' following pattern

So find . -name 'hi' -o -name 'mom' would look for files named hi or mom.

Actions

There are a bunch of actions find can perform. By far the most common and useful one is -exec.

-exec / -execdir

You might need to further manipulate the output of a find command. Usually however, you’ll find that the tools you want to use don’t read from stdin but rather expect the input as params.

You could use xargs for this, but the find command offers a built-in alternative.

You can use -execdir [COMMAND] "{}" \; (or -exec) at the end of your command to achieve ‘pipe like’ functionality.

sh
find . -name 'removeMe' -type f -execdir rm "{}" \;

Here, the [COMMAND] is rm, the "{}" is whatever find found (quoted to avoid shell expansions), and \; indicates the end of the -execdir command.

This example means ‘remove all files named “removeMe” from cwd and its subdirectories’.

There are a couple of things to keep in mind here:

exec vs execdir

Although most of the examples you’ll see around use -exec, this launches the [COMMAND] from wherever you ran find from.

Instead, use -execdir to run the command from the directory used as find’s search parameters.

exec vs shell

When we say that “exec runs a given command”, what we really mean is that find runs the exec application with the given parameters. exec doesn’t really know about shell specific functions, aliases or piping or redirecting outputs.

This is why you’ll commonly see something like -exec bash -c "your_cool_cmd 'params' {}"\;. This way, you can make full use of all of, in this case, bash’s niceties.

\; vs \+

You might find some examples ending with a \+ instead of the \; shown above.

Simply put: \; tells -exec to run its command once per result, while using \+ the command will run only one time taking all results from find as a single parameter.

So \+ is more efficient but, depending on the use case, not always a good fit.

Read more about it here.

Common use cases

Remove empty directories

sh
find . -empty -type d -execdir rm "{}" \+

Detailed results

sh
find . -type f -name '*config*' -ls

Find all config files and print their properties as such:

6454785 4 -rw-r--r-- 1 user user 147 jan 24 12:56 ./tsconfig.json
6454787 4 -rw-r--r-- 1 user user 41 jan 24 12:55 ./config.yml
6427340 4 -rw-r--r-- 1 user user 41 jan 24 12:56 ./node-config.js

Path globs

Say you want the config files under the dotfiles/ directory but you don’t know in which subdirectory they are.

sh
find . -type f -path "./dotfiles/*/config"

Will output the config files somewhere within the dotfiles/ directory.

Exclude specific path

sh
find scr/ -name '*.py' -not -path '*/site-packages/*'

Find all files ending in .py, while discarding the ones under site-packages/.

sh
find . -name 'carmen-sandiego' -printf '%h\n'

Prints the relative path (from cwd) to the results excluding their name.

Count stuff

sh
find src/modules/UserLogin/ -type f -execdir wc -l "{}" \+

Will count how many lines are in each file under the UserLogin module, and print out a total as a bonus!

Fancy things you can do

Clean up

You are done ‘legally’ downloading music and want to clean up the left behind crap from your Music/ directory:

sh
find Music/ -type f -not -iname "*.mp3" -not -iname "*.ogg" -not -iname "*.wma" -not -iname "*.m4a" -execdir rm -r "{}" \;
# This is just an example, for simple use cases prefer something like rm !(*.mp3|*.ogg|*.wma|*.m4a)

You could be more concise with a well put-together regex, the point is that you can achieve this sort of things without it.

More Execdir

Result-dependent sed

sh
find lady/ -type f -name 'gaga' -execdir sed -i 's:dance:Just Dance:g' "{}" \;

Replace all occurrences of dance for Just Dance in any file named exactly gaga within the lady directory.

Learn more about sed.

Remove trailing spaces from directories

sh
find . -name '* ' -execdir bash -c 'mv "$1" "${1%"${1##*[^[:space:]]}"}" "{}"' \;

Yep.

Redirect output

sh
find a_place/ -execdir bash -c 'do_something_cool_on "{}" > {}_processed' \;

Here we create a new file for each match processed by find.

Pipe output

sh
find . -mindepth 1 -maxdepth 1 -type d -execdir sh -c 'ls -1 "{}" | grep -i -q "list|downloaded"' \;

Translates to: ‘List all directories not containing a file called list or download only directly under cwd’.


Other posts you might like