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

find

Usage :

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

Exit Status :

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

Flags :

Flag Usage
-a logical AND :
expr1 -a expr2
expr2 is not evaluated if expr1 is false (Details : search "OPERATORS")
-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
-H Do not follow symlinks, except while processing the command line arguments
-inum n search files having the inode number n
-samefile does the same.
-L dereference symlinks
-maxdepth n target is within n subdirectories level. To investigate the current directory only (no subdirs) : find . -maxdepth 1 ...
-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.
-o logical OR :
expr1 -o expr2
expr2 is not evaluated if expr1 is true
-P Never follow symlinks (default behavior)
-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.
-prune ignore a whole directory tree (details)
-regex -iregex perform a RegExp / case insensitive RegExp search (examples)
-regextype specify the RegExp ruleset to use. By default, find uses the GNU Emacs RegExp ruleset.
-samefile someFile search files sharing the same inode as someFile (i.e. hard links)
-size n Find files bigger than 1KiB : find . -size +1024c
  • The final c is for chars (= bytes).
  • When looking for 0 byte files, consider empty.
-type t search files of type t (list of file types) :
  • b : block special file
  • c : character special file
  • d : directory
  • l : symbolic link. Collides with the -L and -follow options that dereference symlinks.
  • p : FIFO (aka "named pipe")
  • f : regular file
  • 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.

Actions on targets

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 exec/forking reasons (details).
  • 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 \;
    file1	file2
  • touch file1 file2; find . -name "*1" -o -name "*2" -exec ls {} \;
    ./file2
  • touch file1 file2; find . -name "*1" -o -name "*2" -o -name "*3" -exec ls \;
    (nothing)
To execute a command on all results, the solution is to use xargs :
touch file1 file2; find . -name "*1" -o -name "*2" -o -name "*3" | xargs ls
./file1	./file2
-ok command similar to -exec command, but prompts before executing command.

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

Example :

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 containing .cfg
find . -regex "./.*[a|b]c.*"
match files 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 *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 script.pl -a ! -type f -ls
Exclude a directory from the searched zone (aka : more on the prune operator) :
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 -exec ls -l {} \;

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 '*mp4' -exec ls -l {} \; | awk '{ TOTAL += $5} 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 -exec ls -l {} \;" | awk '{ TOTAL += $5 } 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 \;
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 script.pl -print -exec bash -c "myFunction {}" \;
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 '...