Find - “...because one who knows not how to seek will never find...” ― Umberto Eco, Foucault's Pendulum




find searches files / directories and can run any specific action on the found results.

find options startingPoint expression

startingPoint :

Exit Status

Code Condition Details
0 all files were processed successfully. This includes occurrences where find found no match. find ~ -name aFileWithAVeryUncommonName; echo $?
!=0 an error occurred find ~ -name; echo $?
find: missing argument to `-name'



Flag Usage
-daystart For -amin, -atime, -cmin, -ctime, -mmin and -mtime :
  • measure times from the beginning of today rather than from 24 hours ago. In other words : the current day (aka "today") started at 00:00:00, rather than now - 24 hours.
  • only affects tests which appear later on the command line
Albeit causing no error, this flag makes no sense with the *min flags (details).
-H Do not follow symlinks, except while processing the command line arguments
-L dereference symlinks
-maxdepth n target is at most n subdirectories deep. To investigate the current directory only (i.e. no recursion) : find -maxdepth 1
-mindepth n
  • Do not apply any tests or actions at levels less than n levels (n ≥ 0)
  • -mindepth 1 means : process all files except the starting-points, which proves useful —for instance— to list subdirs of the current directory without listing the current directory itself
-mount Don't descend directories on other filesystems
-P Never follow symlinks (default behavior)
-regextype specify the RegExp ruleset to use. By default, find uses the GNU Emacs RegExp ruleset.


Flag Usage
-amin n target was last accessed n minutes ago (Details on Numeric Arguments, details on atime / ctime / mtime)
Before using this, make sure the considered filesystem doesn't use the noatime mount option.
-atime n target was last accessed n*24 hours ago (Details on Numeric Arguments, details on atime / ctime / mtime)
Before using this, make sure the considered filesystem doesn't use the noatime mount option.
-cmin n target's status was last changed n minutes ago (Details on Numeric Arguments, details on atime / ctime / mtime)
-ctime n target's status was last changed n*24 hours ago (Details on Numeric Arguments, details on atime / ctime / mtime)
-empty find empty objects :
  • 0-sized regular files
  • directories containing no regular file and no sub-directory
-group groupName search files owned by the group groupName
-inum n search files having the inode number n
-samefile does the same.
-mmin n target was last modified n minutes ago (Details on Numeric Arguments, details on atime / ctime / mtime)
-mtime n target was last modified n*24 hours ago (Details on Numeric Arguments, details on atime / ctime / mtime)
-name 'pattern'
-iname 'pattern'
target file name matches pattern (such patterns are called globular expressions (i.e. wildcards))
same as above for case-insensitive searches. (Details on "Pattern matching" vs "filename expansion")
In order to protect it against shell expansion, pattern must be quoted when including wildcards. Single quotes seem to do the job in any case.
-path pattern
-ipath pattern
  • path/to/result/file matches pattern (examples)
  • use absolute or relative path in pattern depending on how you specified the place to search into : find somewhere (search "relative", match "relative")
  • use -ipath for case-insensitive search
  • -perm mode
  • -perm -mode
  • -perm /mode
  • target permissions bits are exactly set to mode
  • All of the permissions bits mode are set for target. This performs an "at least" permissions match.examples
  • Any of the permissions bits mode are set for target. This performs an "OR" permissions match.
-regex -iregex perform a RegExp / case insensitive RegExp search (examples)
-samefile someFile search files sharing the same inode as someFile (i.e. hard links)
-size expression expression is [prefix][number][unit] :
  • prefix :
    • + : >
    • - : <
  • number : the number of units (see below) to compare to. Don't forget that the actual size is rounded up to the next unit :
    • -size -1M : will match empty files only
    • -size -1048576c : will match files from 0 to 1,048,575 bytes
  • unit :
    • c : bytes
    • k : KiB
    • M : MiB
    • G : GiB
When looking for 0 byte files, consider empty.
examples :
  • files bigger than 1KiB : find -size +1024c
  • images that are less than 50KiB : find -name *jpg -size -50k -ls
-type t search files of type t (list of file types) :
  • b : block special file
  • c : character special file
  • d : directory
  • f : regular file
  • l : symbolic link. Collides with the -L and -follow options that dereference symlinks.
  • p : FIFO (aka "named pipe")
  • s : socket
-user bob search files owned by Bob
-xtype c find "scans" files (everything is a file, remember ) and filters them to display those matching :
  • if the currently scanned file is not a symlink, -xtype behaves like -type
  • if the file is a symlink :
    • with -H or -P : will match if the target of the symlink is a file of type c
    • with -L : will match if c has the value l (so this will confirm a symlink is a symlink ??? )
This can be used to find broken symlinks.


Operators Usage
-a logical AND :
expr1 -a expr2
  • expr2 is not evaluated if expr1 is false (source)
  • -a is assumed where an operator is missing. Source :
    • man -P 'less -p "expr1 -a expr2"' find
    • man -P 'less -p "Operators$"' find
-o logical OR :
expr1 -o expr2
expr2 is not evaluated if expr1 is true
! expr negates expr
This can be used to exclude file names from the results without using grep -v :
find -type f ! -name '*l'
-not expr same as ! expr, but not POSIX compliant

It is possible to combine expressions with ( ) :

To do so, you'll have to :
  • escape parentheses with \
  • pad the contents of parentheses with [SPACE]
source and details : man -P 'less -p "OPERATORS$"' find
Example :
workDir=$(mktemp -d); prefix='file_'; mkdir -p "$workDir"; cd "$workDir"; for i in 1 2 3; do for j in a b c; do touch "$prefix$i.$j"; done; done; ls -1; echo; find -name "*1*" -o -name "*2*" -name "*b"; echo; find \( -name "*1*" -o -name "*2*" \) -name "*b"; rm "$prefix"*; cd - && rmdir "$workDir"
file_1.a	all the test files we just created

./file_1.b	those having '1' OR ('2' AND 'b')

./file_1.b	those having ('1' OR '2') AND 'b'


Flag Usage
-delete someway similar to -exec rm {} +. -delete conflicts with -prune :
-exec command
  • execute the specified command to each found target. The target is referred to as {}.
  • The find command itself must be terminated, and it's closing character has to be escaped with  \; (space-backslash-semicolon). This closing "tag" may be replaced with  + (space-plus) for performance reasons (exec / forking, to be exact).
  • see some examples
Looks like the -exec part is executed only if the preceding condition is "true" :
  • touch file1 file2; find -name "*1" -o -name "*2" -exec ls {} +
  • touch file{1..5}; find -name "*1" -o -name "*2" -o -name "*3" -exec ls {} +
To execute a command on all results :
  • place the OR condition within the search criteria :
    touch file1 file2; find -name "*[12]" -exec ls {} +
    ./file1	./file2
  • use xargs :
    touch file{1..5}; find -name "*1" -o -name "*2" -o -name "*3" | xargs ls
    ./file1	./file2	./file3
To execute more than a single action on results : see this xargs hack.
Instead of -exec ls -l , you can use -ls :
touch file1 file2; find -name "*1" -ls
If find [options] -ls displays much more lines than find [options], it _may_ be because one of the results is a directory .
-ls short for -exec ls -dils {} +
-ok command similar to -exec command, but prompts before executing command.
  • trying to use -ok with the + termination syntax fails :
    find /tmp -type d -ok chmod u+r {} +
    find: missing argument to `-ok'
  • but works fine with  \;
    find /tmp -type d -ok chmod u+r {} \;
-prune ignore a whole directory tree. This is done by specifying this directory prior to the -prune directive itself (details) :
find -path "directoryToIgnore" -prune

When finding by file name with :

find /some/directory/path/ -name anyFile
on CentOS 6.0, find returns nothing if the search path has no trailing /

Numeric arguments can be specified as :

  • +n for >n
  • -n for <n
  • n for =n


Name-based find :

Find files not matching a pattern :

find /path/to/directory/ ! -name '*php'

Pattern is a RegExp :

find /path/to/a/directory/ -regex ".*\.cfg.+"
match files names containing .cfg
find -regex ".*[ab]c.*"
match files names containing either ac or bc
find -regex ".*06-\(1[56789]\|2.\|3.\).*log"
match logs from the 2nd half of June
find -regextype posix-extended -regex './whatever_[0-9]{2}\..*'
match files which name is whatever_ followed by 2 digits
-regextype posix-extended is mandatory to use interval expressions : {n}
  • this doesn't perform a "search" but a RegExp match on the path+file name. This is why the RegExp pattern must start with something matching the file path (such as .* or ./).
  • some characters that are usually part of the RegExp syntax ...
    • must be escaped : ( ) |
    • need not being escaped : [ ]
  • consider using -iregex for case-insensitive search

Find .wav files in dir1/dir2 and in dir3 :

  • the directory tree is like :
    ├── dir1
    │   ├── ...
    │   │   ├── ...
    │   │   └── ...
    │   ├── dir2
    │   ├── ...
    │   │   ├── ...
    │   │   └── ...
    ├── ...
    │   ├── ...
    ├── dir3
    │   ├── ...
    │   │   ├── ...
    │   │   └── ...
    │   ├── ...
    └── ...
  • find '/path/to/storage' -path '*dir1/dir2*' -o -path '*dir3*' -a -name "*wav"
  • The trick is that, when searching in someDirectory and in someOtherDirectory using -path, any result file can only match 1 of these directories. Hence the necessity of the OR operator between -path specifications.

Find *yml files inside many group_vars directories :

RegExp to the rescue :
find -iregex '.*group_vars.*/.*yml'
Using path :
find -path '*group_vars*' -a -name '*yml'
Even better :
find -path '*group_vars*yml'

Find and list files matching a pattern AND that are not symlinks :

find ~ -name -a ! -type f -ls

Exclude a directory from the searched zone :

By negating the -path value:
find -type f ! -path "*.git*"
With prune :
find -path "./Trash" -prune -o -name '*~' -print
If -print is omitted, the output may not look as expected.
Some random notes while experimenting on this subject :
Build a testing file tree :
tmpDir=$(mktemp -d --tmpdir tmp.dir.XXXXXXXX); nbDirs=3; nbFiles=3; mkdir -p "$tmpDir"; cd "$tmpDir"; for i in $(seq 1 $nbFiles); do touch "myFile_$i" "yourFile_$i"; done; for i in $(seq 1 $nbDirs); do dirName="dir_$i"; mkdir "$dirName"; for j in $(seq 1 $nbFiles); do touch "$dirName/myFile_$j" "$dirName/yourFile_$j"; done; done
Find all myFile :
find -name '*myFile*'
Find all myFile, except those of subdirectories :
find -maxdepth 1 -name '*myFile*'
Find all myFile, except those of dir_2
find -path ./dir_2 -prune -o -name '*myFile*'

If the search region (here .) is specified as a relative path, the path to exclude from the search (here ./dir_2) must be specified as a relative path too. Same goes on with absolute paths : mixing relative and absolute fails.

The prune expression must be considered as a block : -path excludedDir -prune -o. The -o (logical "OR") seems counter intuitive since we are searching for files that are not in excludedDir AND that match the other search criteria. But actually, it works if you consider the way find evaluates -o and -a operators.

Find the newest version of a file :

Let's imagine on every project you create a configuration file that always has the same name. Here's how to find the newest :

find ~/dev/scriptsIQ/applications/ -iname '*createfilesystems*conf' | xargs ls -lt

File attributes-based find :

List files modified less than one hour ago in /etc/, ignore backups, and tar them all :

find /etc/ -mmin -60 | egrep -v '~$' | xargs tar -cf backup.tar --dereference

Find files not belonging to the specified user :

find ! -user bob

Find files bigger than n MB :

find -size +$((n*1024*1024))c

Permission-based find :

directories that are writable by non-owners :
find -type d -a -perm /022
executable JPEG files :
find ~ -type f -iname "*jpg" -a -perm /111 -ls

File content-based find :

Find PHP files containing either "_Pre" or "_Post", and display the matching line number :

  • find ./ -name '*php' | xargs egrep -n '(_Pre|_Post)'
  • grep -En '_P(re|ost)' *php

Find non-empty files :

find -type f -a ! -empty

Directory tree-based find :

Find files only in 2 subdirectories depth :

find -maxdepth 2 -type f

Display the directory tree up to a specified depth :

find /path/to/a/directory -maxdepth 2 | sed -e 's/:$//' -e 's/[^-][^\/]*\//--/g' -e 's/^/ /' -e 's/-/|/'

Display all subdirectories, the number of files they contain, and sort DESC by number of files :

for directory in $(find -type d); do echo -n $(find $directory -maxdepth 1 -type f | wc -l); echo ' '$directory; done | uniq | sort -nr | less

Take action on found targets with exec :

Compute the total size of all found files :

typical case :
find -name '*jpg' -ls | awk '{ TOTAL += $7} END { print TOTAL/1024/1024 " MB"}'
for a list of directories from a list of servers, over SSH :
for server in server1 server2; do for directory in /path/to/dir1 /path/to/dir2; do echo -e "\n$server:$directory"; ssh -q $server "find $directory -ctime +180 -ls" | awk '{ TOTAL += $7 } END { print TOTAL/1024/1024 " MB"}'; done; done

Move found files to another directory :

find -name '*~' -exec mv {} ./Trash +
  • {} will be replaced by the name of the current found file before running mv
  • Anything on the command line is considered as an argument, so the end of command must be explicitly specified with ;. To avoid this ; to be misinterpreted by the shell, it's better to escape it : \;

Move files to trash, except those already in the trash directory :

find -path "./Trash" -prune -o -name '*~' -exec mv {} ./Trash +

Execute several actions for each found target :

find . -iname "*test*" -exec bash -c "separator='--------'; echo '1-'{}; echo '2-'{}; echo \$separator" +
To use variables in this construct, you have to escape their leading $.
find . -iname "*test*" -exec bash -c 'separator='--------'; echo '1-'{}; echo '2-'{}; echo $separator;' +
This works despite using only single quotes '...

Rename files :

find -type f -iregex ".*folder\.jpe?g" -exec bash -c 'newName=$(dirname "{}")/cover.jpg; mv "{}" "$newName"' +

Run a function :

myFunction() {
	doSomething $1

# since 'find' starts a new shell, the function requires to be exported to be available.
export -f myFunction

find . -name -print -exec bash -c "myFunction {}" +