The Linux find
command is a very versatile tool in the pocket of any Linux administrator. It allows you to quickly search for files and take action based on the results.
The basic construct of the command is:
find [options] [path] [expression]
The options
part of the command controls some basic functionality for find
and I’m not going to cover it here. I will instead quickly look at the components for the expression part of the command, and provide some useful examples.
Expressions are composed of tests, actions, global options, positional options and operators:
- Test - returns true or false (e.g.:
-mtime
,-name
,-size
) - Actions - Acts on something (e.g.:
-exec
,-delete
) - Global options - Affect the operations of tests and actions (e.g.:
-depth
,-maxdepth
,) - Positional options - Modifies tests and actions (e.g.:
-follow
,-regextype
) - Operators - Include, join and modify items (e.g.:
-a
,-o
)
Operators
Operators are the logical OR
and AND
of find, as well as negation and expression grouping.
Operator | Description |
---|---|
\( expr \) |
Force precedence |
! or -not |
Negate |
-a or -and |
Logical AND . If two expressions are given without -a find will take it as implied. expr2 is not evaluated if expr1 is false |
-o of -or |
Logical OR . expr2 is not evaluated if expr1 is true |
, |
Both expr1 and expr2 are always evaluated. The value of expr1 is discarded |
You can use the operators for repeated search options with different values.
Example 1: Find files with multiple file extensions
find ${jboss_dplymnt_dir} -type f -size -2k \( \
-name '*.deployed' -o -name '*.dodeploy' \
-o -name '*.skipdeploy' -o -name '*.isdeploying' \
-o -name '*.failed' -o -name '*.isundeploying' \
-o -name '*.pending' -o -name '*.undeployed' \
\)
Example 2: find MOV or MP4 files that do not have ‘h265’ in the file name
find . \( \
! -name '*h265*' -a \( \
-name '*.mp4' -o -name '*.MP4' \
-o -name '*.mov' -o -name '*.MOV' \
\) \
\)
Or to negate search options.
Example 3: find files that don’t finish with the ‘.log’ extension
find . -type f -not -name '*.log'
Example 4: Excludes everything in the folder named ‘directory’
find -name "*.js" -not -path "./directory/*"
Global Options
Two very useful global options are -maxdepth
and -mindepth
.
- maxdepth levels: Descend at most levels (a non-negative integer) levels of directories below the starting-points.
-maxdepth 0
means only apply the tests and actions to the starting-points themselves. - mindepth levels: Do not apply any tests or actions at levels less than levels (a non-negative integer).
-mindepth 1
means process all files except the starting-points.
Example 5: find all files with ‘.txt’ extension on the current dir and do not descend into subdirectories
find . -maxdepth 1 -name '*.txt'
Tests
This is where you can target specific properties about the files that you are searching for. Some of my preferred tests are:
-iname
- Like-name
, but the match is case insensitive-size
- Matches based on size (e.g.:-size -2k
,-size +1G
)-user
- File Belongs to username-newer
- File is newer than file. It can be a powerful option
Example 6: Find files for user
find . -type f -user linustorvalds
Example 7: Find files larger than 1GB
find . -type f -size +1G
Example 8: Here’s a hidden gem! Let’s say you need to find what files a program is creating. All you need to do is create a file, run the program, and then use -newer
to find what files were created
# Create a file
touch file_marker
# Here I run the script or program
./my_script.sh
# Now I can use the file I created to find newer files that were created by the script
find . -type f -newer 'file_marker'
Actions
Actions will execute the specified action against the matched filenames. Some useful actions are:
-ls
- Lists matched files withls -dils
-delete
- Deletes the matched files-exec
- Execute the specified command against the files-ok
- Prompts user for before executing command-print0
- Prints the full name of the matched files followed by a null character
Real-life Scenarios
Get a confirmation prompt
Use -ok
to get a confirmation prompt before executing the action on the matched files.
find . -type f -name '*.txt' -ok rm -rf {} \;
< rm ... ./file_10.txt > ? y
< rm ... ./file_9.txt > ? y
< rm ... ./file_8.txt > ?
Deleting files
Whenever possible, use -delete
instead of -exec rm -rf {} \;
.
find . -type f -name "*.bak" -exec rm -f {} \;
Instead use:
find . -type f -name "*.bak" -delete
Using xargs
and {} +
The command bellow will run into a problem if files or directories with embedded spaces in their names are encountered. xargs
will treat each part of that name as a separate argument:
find /usr/include -name "*.h" -type f | xargs grep "#define UINT"
There are two ways to avoid that. You could tell both find
and xargs
to use a NUL
character as a separator:
find /usr/include -name "*.h" -type f -print0 | xargs -0 grep "#define UINT"
Or, you could avoid use of xargs
altogether and let find
invoke grep
directly:
find /usr/include -name "*.h" -type f -exec grep "#define UINT" {} +
That final +
tells find
that grep
will accept multiple file name arguments. Like xargs
, find
will put as many names as possible into each invocation of grep
.
Find and Move
Find files and move them to a different location.
-v
- For verbose-t
- Move allSOURCE
arguments intoDIRECTORY
-exec command {} +
- As we saw before, this variant of the-exec
action runs the specified command on the selected files, but the command line is built by appending each selected file name at the end; the total number of invocations of the command will be much less than the number of matched files
No need for \;
at the end.
find . -maxdepth 1 -name 'sa-*' -exec mv -v -t rsa-todelete/ {} +
Search PDF content
Here we search the contents of PDF files for specif text. Two options are shown below, each require the install of an additional package (pdfgrep
and ripgrep-all
).
With pdfgrep
:
find . -iname '*.pdf' -exec pdfgrep [pattern] {} +
With ripgrep-all
:
find . -iname '*.pdf' | xargs rga -H PURGE_TRAN_LOG.sh
Check if files exists
One of the problems with find
is that it doesn’t return true
or false
based on search results. But we can still use it on our if
condition by greping the result.
if find /var/log -name '*.log' | grep -q log ; then
echo "File exists"
fi
Conclusion
You should now have a better understanding of the find
command, as well as some nifty use cases to impress you co-workers.
If you found something useful or want to share an interesting use for find
, please leave a comment below.