A needle in a haystack!
Find helps you locate files based on a variety of parameters, and even allows you to manipulate them!
Keep in mind
Not all Find implementations are created equal.
This post references the GNU version, although hopefully most of the information is generic enough to be of use in most Find implementations.
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:
find . -name 'config'
Which translates to “Find anything named exactly config
within cwd
and its contained directories”.
This would print the path (relative to where Find is launched) for both 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
Not so Basics
For simplicity’s sake, I’ve grouped these options under three categories: Filters, Operators and Actions.
This is completely made up and only serves as a teaching device.
I would however recommend using them in the order they are presented.
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
As far as Find is concerned, the name of a file is simply the last portion of its path.
Say you want to find all files within a directory called something
, but there are many such directories under cwd
.
This simply means you are looking for files with something
as a part of their paths.
find . -type f -path '*something*'
As you can see, the EXP
part of the command takes in reduced regex (which is why it only matches the exact string by default).
Here, we include the wildcard *
since it is evaluating the full relative path of the files (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: 0
is the directory passed to Find (cwd
in our examples so far), 1
is its direct children directories, and so on.
-
find . -maxdepth 0 -type f -name 'whoami'
: look for a file namedwhoami
only within the starting directory (ignoring its child directories). -
find . -mindepth 1 -type f -name 'whoami'
: look for a file namedwhoami
in all directories undercwd
exceptcwd
itself.
Operators
Mix and match (or negate) multiple searches:
-not -> negate following pattern
-a -> 'and' following pattern
-o -> 'or' following pattern
Due to the way Find processes its filters, you might find the -o
operator to not behave as you would expect.
Consider using -regex
with ||
between the two patterns if that’s the case.
Actions
There are a couple of actions that Find can perform, but by far the most common and useful one is -exec
.
-exec / -execdir
You might be tempted to use a pipe to further manipulate the output.
This can get tricky due to the way Find outputs its results.
You can use -execdir [COMMAND] "{}" \;
(or -exec
) at the end of your command to achieve ‘pipe like’ functionality.
|
|
Here, the COMMAND
is rm
, the "{}"
is whatever Find found (quoted to avoid shell expansions), and \;
indicates the end of the -execdir
command.
This means ‘remove all files named ‘removeMe’ from cwd
downwards’.
Notes on exec
A couple of things to keep in mind:
exec vs execdir
Although most of the examples you’ll see around use -exec
, this launches the command from the starting directory which is hardly safe or expected behavior.
Instead, use -execdir
to run the command from the directory matching 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 actually open a new process with a new shell, it runs within the shell it was executed in, sort of like a sandboxed environment.
This means that shell specific functions, aliases and scripts won’t make any sense to exec
, nor will piping or redirecting outputs.
This is why you’ll commonly see something like -exec bash -c "your_cool_cmd 'params' {}"\;
(or with sh -c
on more minimalistic systems).
\;
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
find . -empty -type d -execdir rm "{}" \+
Detailed results
find . -type f -name '*config*' -ls
Find all config
files (lang.config
, config.lang
, etc.) and print their properties as such:
6454785 4 -rw-r--r-- 1 eric eric 147 jan 24 12:56 ./tsconfig.json
6454787 4 -rw-r--r-- 1 eric eric 41 jan 24 12:55 ./config.yml
6427340 4 -rw-r--r-- 1 eric eric 41 jan 24 12:56 ./node-config.js
Path globs
Say you want the config
files under the tests/
directory (not knowing in which subdirectory it is) but you don’t want Find to consider all the config
files under src/
.
find . -type f -path "./tests/*/config"
Will only output the config
files somewhere within the tests/
directory.
Exclude specific path
find scr/ -name '*.py' -not -path '*/site-packages/*'
Find all files ending in .py
, while discarding the ones in the site-packages/
directory.
Print path to results
Know the name of the file/s but not the path?
find . -name 'carmen-sandiego' -printf '%h\n'
Prints the relative path (from cwd
) to the results excluding their name.
Count stuff
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:
|
|
Could you be more concise with a well put-together regex?
Sure, the point is that you can achieve quite a lot without it.
Plus, the same can be done with -path
, in which case the regex might get a bit hairy.
More Execdir
Result-dependent Sed
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
find . -name '* ' -execdir bash -c 'mv "$1" "${1%"${1##*[^[:space:]]}"}" "{}"' \;
Yep.
Redirect output
find 'a_place' -execdir bash -c 'do_something_cool_on "{}" > {}_processed' \;
Here we create a new file for each processed match reached by Find.
Pipe output
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
’