Here are some tips for using find (aka gfind), which is available on Linux, Mac, *nix, *BSD, and Windows WSL.

find <dir> -name <name> [-print]

Simplest find

$ find .
.
./a.txt
./b.txt

$ find /etc $HOME /tmp .
# find in multiple places...

If path is “.”, it seems to use relative path, not full path when retrieving filenames. Instead of using “.”, one can use pwd to get full path.

$ find `pwd`
/Users/ccp
/Users/ccp/a.txt
/Users/ccp/b.txt

List all files in current dir

find . -type f

List all directories in current dir

find . -mindepth 1 type d
  • It lists not only subdirectories but also the search path (or “.” in this case). To avoid printing current path, use -mindepth 1

Find files in multiple directories (foo/, bar/, baz/)

find foo bar baz -iname "*.rb" | view -
  • and open in vim read-only

Find all dot files but not git dot files

find . -name ".*"  ! -name ".git*"

Find all non-dot files

find . -name '[!.]*'

Examples

find . -name my.txt
# Finds matching files

find / -name sound*
# Finds all file starting with sound starting from the root

find . -name "*txt"
# Find all file ending with "txt" in current directory.  Wildcard needs to be enclosed in quotes ("")

find . -u suzie
# find any files owned by Suzie.

find . -u suzie -type d -name "*data"
# find any directories owned by Suzie that matches the "data"

find . -name "*.lock" -exec chmod 777 {} \;
# find all *.lock and chmod it

find . -name "*.ogg" -mtime -7
# find *.ogg created within past 7 days

Change permissions for Wordpress directory

find /path/to/your/wordpress/install/ -type d -exec chmod 755 {} \;
find /path/to/your/wordpress/install/ -type f -exec chmod 644 {} \;

Change permission, give group access to all directories

find . -type d -exec chmod g+x {} \;

Calculate size of all files using find

find . -iname "*.png" -ls | awk '{s += $7} END {print s}'
> 2076723

Same without awk

find . -iname "*.png" -print0 | xargs -0 du -ch | tail -1
> 2.2M    total

find empty files

find  /path/... -type f -empty

// find empty files and delete them
find  /path/to/dest -type f -empty -delete

find empty dir

find  /path/to/dest -type d -empty

Test find by using echo after -exec (ie do a dry-run)

find `pwd` -type d -mindepth 1 -exec echo ln -s {} ~{} ";"

find all files which are NOT png in /tmp and its sub directories

find /tmp ! -name "*.png"

find by permission

find ~ -perm 644

find all directories (non-recursive) except “.git/”

find . -maxdepth 1 -mindepth 1 \! -name "*.git" -type d

Alternative to -name

find ./public/*.html     # depth = 1
find ./public/**/*.html  # depth = recursive

find names and apply iconv to filename (not content of file)!! http://stackoverflow.com/questions/9930484/change-file-names-with-find-and-iconv

Find files over 100MB

$find . -size +100M

calculate the size of all files found by find

There are for sure hundreds of ways to achieve this…I liked the combination of a simple find with a short and sweet awk function:

tmp > find . -iname "*.png" -ls | awk '{s += $7} END {print s}'
2076723

As some people on hn pointed out awk is probably not the simplest solution for summing up space usage. So I include an example inspired from this blog.

tmp > find . -iname "*.png" -print0 | xargs -0 du -ch | tail -1
2.2M    total

find based on Date/time

Find files that were changed in last x days/ minutes

# find files that were changed in the last 10 minutes
find / -mmin -10

# files modified between now and 1 day ago
find / -mtime -0 # days, not minutes, (within past 24 hours)
find / -mtime -1 # same as above

# find files modified between 11 and 19 minutes ago
find / -mmin +10 -mmin -20

# Find files created within the last 7 days
$find . -mtime -7

# Find files older than 14 days
$find . -mtime +14 -type f -name '*.gz'

# find all files that has been modified within last 24 hours and delete them.
#?? needs `\;` at end???
find . -mtime 0 -exec rm { }

# Delete files older than 14 days
$find *.gz -mtime +14 -type f -exec rm {} \;
  • also see -cmin, -ctime (changes made to file’s node -ie permission, metadata, but not content of file)

linux - How can I find the oldest file in a directory tree - Super User

find -type f -printf '%T+ %p\n' | sort | head -n 1

My modification: show newest file using POSIX/Epoch time (since 1970)

find -type f -printf '%T@ %p\n' | sort -r | head -n 1
  • -printf should end with \n in order to be able to use pipe

Show only the POSIX timestamp for the newest file

find $NOTES_PATH -type f -printf '%T@\n' | sort -r | head -n 1

Find files that are newer than another file

find . -newer <File_To_Compare> ...

find . -newer /tmp/old.log -exec processNew.sh '{}' \;

Example: Unix/Solaris: Using the -newer option of the find command To find all files that were modified 20 minutes ago (useful for checking the system to see what changed when a problem occurs)

  1. create or use a file, set timestamp to 20 minutes ago

    touch -mt 12251350 /tmp/empty_file
    # set it accordingly
  2. find all files in entire hard drive that have changed since 13:50

    find / -newer /tmp/empty_file -print -local
    * `-local` (only in Sun), use local hard drive, not NFS

print

use -print to print filename

find . -print ... 

alternatives:

find . -exec echo '{}' \;
find . -printf "%p\n" ... # GNU only

print filename only, not its dirname

find . -exec basename '{}' \;
find . -execdir echo '{}' \;
find . -printf "%f\n" # GNU only

-printf (GNU only)

%p : full path filename
%f : filename only
%s : size of file (bytes)
%T{..} : last modified time
%T@ : in POSIX / Epoch time with fraction
%T+ : date+time (Gnu only)

find -type f -printf '%p takes %s bytes\n'
  • see manpage for more detail

-exec, -execdir

find . -exec <COMMAND> \;

It must end with \; or ';' or +

find . -exec echo "hello" \;
find . -exec echo "hello" ';'

'{}': matching file’s filename

find . -exec cat '{}' \;

find . -name *.txt -exec cat '{}' ';'
find . -name "*.txt" -exec cat '{}' \;
  • {} should be quoted ('{}') to protect from shell interpretation

\; vs +

  • \;: runs command for each match; this is what we usually want.
  • +: runs command once with all the match as a parameter
    • use for diff

Example:

find . -type f -exec echo '{}' +
> 1.txt 2.txt 3.txt
# `echo 1.txt 2.txt 3.txt`

find . -type f -exec echo '{}' \;
> 1.txt 
> 2.txt 
> 3.txt
# echo 1.txt 
# echo 2.txt 
# echo 3.txt

-execdir: same as -exec, except that <command> runs inside the directory that holds the current file.

find . -execdir complicated.sh '{}'
  • -exec: {} contains filename relative to pwd
  • -execdir: {} contains filename only, since it runs inside the directory of matched file

Difference between -exec and -execdir

find . -exec echo '{}' \;
> ./src/2017-12-01-post1/1.html
> ./src/2017-12-15-post2/2.html 

find . -execdir echo '{}' \;
> ./1.html
> ./2.html

Alternative: -exec sh -c '<commands>' sh {} \;

find . -name "*.txt" -exec sh -c 'echo "Hello" > "$1"' sh {} \;
  • complicated commands, invokes sh or bash
  • chaining more commands
  • "$1": matched filename, use this instead of {}
  • -exec bash -c for bash

Bad:

find . -name "*.txt" -exec sh -c 'echo "Hello" > {}' sh {} \;

Multiple commands

There can be multiple exec in a single find, using long chaining

find . -exec .... {} ";" -exec ...{} ";" -exec ...{} ";"
  • similar to as shell’s double-ampersand: cmd 1 && cmd 2 && cmd 3 && ..
  • Second command will only run if the first one returns successfully.
  • ";" == ';' == \;

To run multiple commands irregardless of success of each commands:

find . -exec sh -c 'echo "$1"; date; stat "$1"' sh {} \;
  • "$1" is used instead of {} if inside sh

xargs

xargs must be preceded by -print0 (which handles space in the filename correctly). -print0 must be the last argument of find.

find . -exec doSomething...

same as using xargs using pipe

find . -print0 | xargs -0 ....

xargs -0 is required to force NUL instead of space
xargs -r (Linux/Gnu only) doesn't run command if stdin is empty. This prevents error where argument is missing in command. Nice to have it in Linux, but not available on BSD or Mac.

Examples:

find ./folder_of_images -name '*.jpg' | xargs mogrify -strip

-ok, -okdir

-ok: like -exec but asks user first -okdir: like -execdir but asks user first

find . -type f -ok cat '{}' \;

Delete files using find

Careful with delete. Always put -delete after -name, not before or it will delete ALL!!!

$find . -name *.pyc -delete # OK
$find . -delete -name ....   # WRONG! WILL DELETE ALL!!!

Better

find . -name '*.pyc' | xargs rm

The one-liner, run from the folder you want to clean (for cron, change the “.” to the volume’s path):

# http://macosx.com/forums/archive/t-28539.html
find . -name ._* -exec rm '{}' ';'

find . -name "*.pyc" -exec rm -f {} \;

3 ways to delete

find / -name "*.tmp" -print0 | xargs -0 /bin/rm -f
    # without -print0, -0, it will fail on filename containing space. Alt: use {}+
find / -name "*.tmp" -exec /bin/rm -f '{}' \;
find / -name "*.tmp" -delete

find options

  • -name match name
  • -iname case-insensitive match name
  • -d: depth-first instead. Files are acted first, directories are last
  • -delete use instead of -exec rm { } \;
  • -maxdepth n go only up to n depth.
    • -maxdepth 0 limit whole search to cmd line argument
    • -maxdepth 1 only in current directory and not search sub directories
  • -mindepth n: find from at least n depth, useful for ignoring the root path.
    • -mindepth 1 ignore root path. This avoids the problem that occurs when find /some/path -type d because if it path is not “.”, then it includes all dir including ~, or whatever the path is.
  • -x (was -xdev) prevent descending into dir from different device
  • -empty only empty files (size=0k) or empty directories (no files inside) are listed
  • -exec utility run a command. It must end with ";" or \; or +
    • {} gets expanded to pathname of current file. Sometimes it’s surrounded by single quote
    • {}+ is similar to xargs, prevents arguments from being too long for system to handle, and runs command with groups of argument at a time, instead of once per each argument. This is a better than using -print0 | xargs
  • -execdir same as -exec, but executed only for dir. {} is unqualified.
  • -regex
  • -print prints path. This is implied, by default when there’s no other command such as -exec, …
  • -perm find by permission. -perm 644, -perm -o=x,
  • -size find by size
  • -mtime

  • -printf is not available on Mac,BSD. It is GNU extension. (See Mac)

Misc

Useful alias for finding certain files

alias f="find . -name"
f '*.png'   # find all png files in current dir
  • alias find as gfind in Mac/BSD, if needed

  • see my example of dotfiles script.

Mac Notes

BSD find and GNU find

  • For full compatibility, Use Gnu find (named gfind) instead of built-in BSD find.
  • Gnu/Linux find has different option than Mac/BSD find
  • -printf is not available on Mac/BSD. It is GNU extension.
  • Install and use Gnu find:

    brew install findutils
    # or
    brew install coreutils
    
    gfind ... -printf ...

Find + grep to find text in a resource file

Alternative

locate

  • similar to Mac’s Spotlight search

fd (find-clone in Rust)

mac: brew install fd

fd test

References