How to Use Command Line Find

April 2, 2020

Knowing how to use command line find will help you search for files and directories quickly. Below are the things that will be covered:

# Why Learn Find Why do we need to learn find? Can't we just use tools like [ripgrep](https://github.com/BurntSushi/ripgrep), [ag](https://github.com/ggreer/the_silver_searcher), or browse through GUI manually?

Here are some advantages find has:

  1. It is found in most terminal.
  2. Searching from GUI can be tedious and hard to filter.
  3. Although ripgrep and ag may have better features than find, they are not universally available. If you work with remote servers, you may find find more convenient (pun intended).

You don't have to learn every single feature to be productive, just learn the important ones. I believe Pareto's Principle (80/20 law) applies here: learning 20% of find's feature should cover 80% of use cases.

# Syntax and Patterns

Here is the basic syntax:

find location options

For 95-99% of my usage, I use . for location (current directory). find will use current directory as starting path and search recursively. You can use absolute or relative path location, like /Users/iggy/projects/ or ./projects.

Let's talk about options. There are two important options: -name and -path. I think you can get a lot of milage with these two. When describing name and path, you can use basic patterns: [, ], *, and ? (there are more patterns). Note that they are not regex. I will explain them later, but just want to keep in mind.

For more information about patterns, check this out.

# Find by Name

The -name option means the last component of pathname. If I want to search all files containing the name "model.rb?":

find . -name "model.rb"

This returns everything that matches "model.rb" exactly. But what if we want to search for "user_model.rb", or "address_model.rb"? To find everything that contains "model.rb", we can use wildcard (*) pattern to match zero or more characters.

find . -name "*model.rb"

To find anything containing the word "address":

find . -name "*address*"

Note: -name does not mean file name. It could return a directory name. If I have an address directory (./src/components/address/), find it will return this directory too.

# Find by Path

Recall that -name searches for the last component of pathname. To find by anything in the path, we use -path.

To find all files/directories inside "controllers":

find . -path "*controllers*"

By the way, if you do

find . -path "*controllers"

It returns just the "controllers" directories.

Find treats forward slash (/) as normal characters. If you are looking specifically for "client/components":

find . -path "*client/components*"

It returns all path containing client followed by components

If you need to search for either "model", "modal", or "modes", you can use ? to match any one character.

find . -path "*mod??"

This will match "mod", followed by any two characters (model/modal/modes). Remember, ? is not a regex pattern (in regex ? means 0 or more). Here it means any one character.

If you just want to search for either model or modal, you can do:

find . -path "*mod[ae]l"

[...] matches exactly any one character enclosed. In addition, you can give it a range like [a-z], [A-Z], or [0-9]. You can also mix the ranges [a-zA-Z0-9].

If you have "modalA", "modalB", ... "modalZ" and "modal0", "modal1", ... "modal9" and you want to return only the paths with a-z suffix (you do not want to search for numbers):

find . -path "*modal[^0-9]"

Adding ^ as first character inside [...] negates the match inside []. This means "give me anything EXCEPT modal that ends with 0-9".

# Or To search for either A or B, `find` has an `-or` option:
find . -name "*email*" -or -name "*address*"

This searches for either "email" or "address". You can mix and match any options, like name or path. You can also use parentheses to group the conditions:

find . -name "*email.rb" -or \( -path "*deserializers*" -name "*address*" \)

This searches either:

  • Files that end with email.rb, or
  • Files/directories containing "deserializers" in its path and name containing address.
# Not What if we want to search for everything except "address"? Find accepts `-not`, we can do:
find . -not -name "*address*"

Keep in mind that order matters. -not must be followed by an option. find . -name -not "*address*" won't work because -not is placed after -name.

We can also use ! instead of -not.

# Find File / Directory Only

So far when we search we have been getting results for either files of directories. What if we we want to search for only files or only directories?

We can filter it with -type option.

  • For directory, w use -type d
  • For file, we use -type f

There are more file types ("block special", "character special", "symbolic link", "FIFO", "socket"), but I think files and directories are the two that are used most.

To find all files containing "model" in path, we do:

find . -type f -path "*model*"

To search all directories containing the word "model" in its path:

find . -type d -path *model*
# Regex

Find also accepts -regex option:

find . -regex ".*address.rb"

Note that when we're using regex, find matches against the entire relative path. What this means is, find returns matches with relative path (./), for example:

./src/client/whatever.js
./src/client/frontend/
./server.js

Keep this pattern in mind when you are describing your regex.

# Find by Size You can find files based on their file size (rounded up 512b):
find . -size +1M

Finds files 1 Megabyte or greater.

Here are different file size options that find accepts:

k   kilobytes   (1024 bytes)
M   megabytes   (1024 kilobytes)
G   gigabytes   (1024 megabytes)
T   terabytes   (1024 gigabytes)
P   petabytes   (1024 terabytes)
# Execute Command Line

Sometimes it is not enough to just find files. Sometimes we need to execute command line commands into our find results. Find has -exec command for that. Let's look at some examples:

find . -name "*model.rb" -exec cat {} ";"
find . -type d -path "*model*" -exec ls -l {} ";"

You can also chain multiple execs together

find . -name "*model.rb" -exec grep -q to_hash {} ";" -exec cat {} ";"

This searches for all results containing "model.rb" in their path's last component, run grep quietly (-q) to look for lines containing "to_hash" string, then cat these files.

-exec is a useful feature to learn. To learn more about -exec, here are some resources to get started:

# Conclusion I think this is a good place to stop. This knowledge should be sufficient to get you started and be productive. I cannot recommend `man find` enough. There are many more useful options that I didn't mention here. Experiment with different command lines, use it everyday, and more importantly, have fun!

Thanks for reading. Happy coding!

# Resources - `man find` - [How do I find a file by filename in Mac OSX terminal?](https://superuser.com/questions/226566/how-do-i-find-a-file-by-filename-in-mac-osx-terminal) - [Find Command: File Path vs -name Argument](https://unix.stackexchange.com/questions/171065/find-command-file-path-vs-name-argument) - [Shell Pattern Matching](https://www.gnu.org/software/findutils/manual/html_node/find_html/Shell-Pattern-Matching.html) - [How to use regex with find command? ](https://stackoverflow.com/questions/6844785/how-to-use-regex-with-find-command)
© Copyright 2021 Igor Irianto