find descends into a filesystem hierarchy below dir (when omitted the current directory) and evaluates expression (if none given then -print) for each file or directory it encounters.
find's business is evaluating expressions - not locating files. Yes, find certainly locates files; but that's really just a side effect. For me, understanding this point was the conceptual breakthrough that made find much more useful.
My first aha experience was to discover that tests and actions are by default combined with -a (logical and) if the operator is missing.
The second aha experience was to find out that find evaluates its arguments lazily.
In the following example, each file or directory name is compared with ./bin/debug.
If the name differs, -path ./bin/debug returns false and there is no point in also evaluating -prune (which is implicitly connected to -path with -and). Because -path … -and -prune is false, find needs to also evaluate -print to check if the entire expression is true.
However, if the name matches, find also evaluates -prune to check if -path ./bin/debug -and -prune is true. If the path is a directory, -prune evaluates to true and find does not descend into this directory. The name of the directory in that case is not printed because -path … -prune is true and there is no point in also evaluating -print.
$ find . -path ./bin/debug -prune -o -print
Tests (criteria)
The test operators check various file properties and return either true or false.
Some tests take a numerical value (marked as n in the table below). In this case, the value of n can be +n (value is greater than n), -n (value is less than n) or simply n (value must be exactlyn).
Some of these tests come in a case-sensitive and case-insensitive variant (-i…).
Variants of the tests that text case insensitiveli are listed in the 3rd column.
Checks if regular expression matches whole path of file (see also here). Specify regex dialect with the -regextype option
-iregex
-samefile name
-size n[cwbkMG]
File size. c = bytes, b = blocks (512 bytes, the default), w: two-byte words, k: kibi-bytes (1024), M: Mebi-bytes (1024²), G: 1024³
-true
-type x
x is one of b (block device), c (character device), d (directory), p (named pipe), f (regular file), l (symbolic link), s (socket), D (door, Solaris only?). (Compare with the shell command test)
-uid n
-used n
-user uname
Files that belong to a given user, compare with -group
Sets the regular expression dialect used for -regex and -iregex. Valid regex types seem to be: findutils-default, ed, emacs, gnu-awk, grep, posix-awk, awk, posix-basic, posix-egrep, egrep, posix-extended, posix-minimal-basic, sed
-warn, -nowarn
Turn on or off warnings.
Actions
-delete
Deletes the file or empty directory and returns true if successful. Using -delete automatically turns on the -depth option.
-exec cmd ;
Replaces {} in cmd with the current file name then executes the cmd with the working directory begin the starting directory. Returns true if cmd exits with 0?
-exec cmd {} +
Similar to above, but performs an xargs like assembling of the command. Always returns true.
-execdir cmd, -execdir command {} +
Like -exec but command is executed with the working directory set to the directory where the file resides.
-fls file
Like -ls but write to file like -fprint.
-fprint file
Writes full filename to file and returns true.
-fprint0 file
Like -print0 but write to file like -fprint.
-fprintf file format
-ls
ls -dils on current file, return true
-ok cmd ;
Like -exec but ask user before executing command.
-okdir cmd ;
Like -execdir but ask user before executing command.
-print
Print full incl. new line name on stdout, return true.
-print0
Print full name followed by NUL character on stdout. (Compare to -0 option of xargs)
If file is a directory, do not descend into it and return true. If -depth is also specified, -prune has no effect. (Note that -depth is implied by -delete).
-quit
Immediately exit the find process. Useful to check for the existence of a given file: find $dir -name xyz -print -quit
Search in path names
The -path option concatenates the path and filename to match files.
The following command finds files like ./abc/def/ghi/jkl:
find . -path '*bc*f*g*l'
-inum
Finds the file with an inode (that can be found for example with ls -i.
Find files that were changed during a certain period
During the last 5 minutes
$ find . -cmin -5
Note the minus in front of the five: this finds files changed between 0 and 5 minutes. with -cmin 5 only files changed exactly five minutes ago would be found. -cmin +5 find files changed earlier than five minuts ago.
GNU understands date reference strings that can be given to date -d.
$ find . -newermt '5 minutes ago'
During the last n days
Similarly, files that were changed during the last week can be found so
$ find . -ctime -7
Files that are not owned by someone
With the exclamation mark, a premisse can be negated. This allows to search for files thare not owned by some specific user:
$ find . ! -user rene -print
Excluding directories
Directries can be excluded with -path ./path/do/excluded/dir -prune.
Note -prune (as -print) always returns true.
Therefore, it will probably be followed by -o.
I most cases, imho, -prune should be combined with -path (not with -name).
Find all files that contain a b in the filename but are not located under ./git:
The -perm option allows to find files with specific permissions (such as readable by me, writable by group or executable by anyone).
Creating some test files
In order to test this -perm option, we create some test files. The following script creates each possible file mode, that is in total 84 (= 4096).
# vi: ft=sh
[[ -d files ]] && rm -rf files
mkdir files
# Iterate over each possible mode
for file_mode_octal in {0..7}{0..7}{0..7}{0..7}; do
# Create a file …
touch x
# … and change its mode
chmod $file_mode_octal x
# Use ls's long listing format to determine the
# humanly readable flags.
flags_human_readable=$(ls -l x | cut -d' ' -f 1)
# We have to remove the first character from flags_human_readable,
# because signifies if the object is a file, directory, socket etc.
# This # information is uninteresting for now:
flags_human_readable=${flags_human_readable:1}
# We move the file and name it with both the octal and the humanly
# readable representation of the file:
mv x files/${file_mode_octal}___${flags_human_readable}
done
The following command finds files that are owned by the grouptty and have the setgid bit set:
$ find /usr/bin -type f -group tty -perm -g=s
Regular expression
The -regex pattern evaluates to true if the regular expression pattern matches the entire path, i. e. as though pattern was enclosed in ^…$.
The following command finds all files that have ancestor directory (immediate or not) named release-notes and the text 1.7 in their file name:
find -type f -regex '.*/relase-notes/.*1\.7.*'
Files that are not world readable
find files -not -perm /o=r
-regextype
-regextype allows to choose the regular expression syntax format.
I believe the format that comes closes to Perl Compatible Regex Expressions (PCRE) is either gnu-awk or posix-awk.
Find files that contain either 32 or 64 in their name:
$ find -regextype posix-awk -regex '.*(32|64).*'
Recursively find files containing a regular pattern
The following command recursively finds files that contain a regular expressions.
In order to prevent Permission denied error messages, the command prunes unreadable directories and then limits the grep command to files that are readable.
Because files in the .git directory usually not interesting, the command also prunes it:
$ find . \
-path ./.git -prune -o \
-type d ! -readable -prune -o \
-type f -readable -exec grep -i 'find me if.*can' {} \;
Unfortunately, the Permission denied error message might still occur for directories that are not readable.
Find executables
-execute finds executable files and directories. In order to just find executables (such as binaries or shell scripts), the search must be further restricted to files (-type f):
find -executable -type f
The previous command also finds shared objects (which typically end in .so or .so.6.1). If these should not be included in the result, they can be excluded with a regular expression:
find -executable -type f -not -regex '.*\.so\(\.\d\)?.*'
Remove .DS_Store files and __MACOSX directories
find . \( -type f -name '.DS_Store' -o -type d -name '__MACOSX' \) -exec rm -rf {} +
Apparently, find distinguishes between global options (such as -maxdepth, -xdev, -noleaf …) and non option arguments (such as -type) that are used in expressions.
It seems that the global options must precede the non option arguments. find . -type d maxdepth 1 would consequently give the following warning: you have specified the -maxdepth option after a non-option argument -type, but options are not positional (-maxdepth affects tests specified before it as well as those specified after it). Please specify options before other arguments.
Switching -maxdepth and -type makes the warning go away.