Wednesday, 30 May 2012

Find Command Examples


List filenames ending in .mp3, searching in the current folder and all subfolders:
$ find . -name "*.mp3"

List filenames matching the name Alice or ALICE (case insensitive), search in the current folder (.) and all subfolders:
$ find . -iname "alice" -print0

List filenames matching the name Alice or ALICE (case insensitive), search in the current folder (.) only:
$ find . -maxdepth 1 -iname "alice" -print0

List filenames ending in .mp3, searching in the music folder and subfolders:
$ find ./music -name "*.mp3"

List files with the exact name: Sales_document.doc in ./work and subfolders:
$ find ./work -name Sales_document.doc

List all files that belong to the user Maude:
$ find . -user Maude -print0

List all the directory and sub-directory names:
$ find . -type d

List all files in those sub-directories (but not the directory names)
$ find . -type f

List all the file links:
$ find . -type l

List all files (and subdirectories) in your home directory:
$ find $HOME

Find files that are over a gigabyte in size:
$ find ~/Movies -size +1024M

Find files that are over 1 GB but less than 20 GB in size:
$ find ~/Movies -size +1024M -size -20480M -print0

Find files have been modified within the last day:
$ find ~/Movies -mtime -1

Find files have been modified within the last 30 minutes:
$ find ~/Movies -mmin -30

Find .doc files that also start with 'questionnaire' (AND)
$ find . -name '*.doc' -name questionnaire*

List all files beginning with 'memo' and owned by Maude (AND)
$ find . -name 'memo*' -user Maude

Find .doc files that do NOT start with 'Accounts' (NOT)
$ find . -name '*.doc' ! -name Accounts*

Find files named 'secrets' in or below the directory /tmp and delete them. Note that this will work incorrectly if there are any filenames containing newlines, single or double quotes, or spaces:
$ find /tmp -name secrets -type f -print | xargs /bin/rm -f

Find files named 'secrets' in or below the directory /tmp and delete them, processing filenames in such a way that file or directory names containing single or double quotes, spaces or newlines are correctly handled. The -name test comes before the -type test in order to avoid having to call stat(2) on every file.
$ find /tmp -name secrets -type f -print0 | xargs -0 /bin/rm -f

Run 'myapp' on every file in or below the current directory. Notice that the braces are enclosed in single quote marks to protect them from interpretation as shell script punctuation. The semicolon is similarly protected by the use of a backslash, though ';' could have been used in that case also.
find . -type f -exec myapp '{}' \;

Traverse the filesystem just once, listing setuid files and directories into /root/suid.txt and large files into /root/big.txt.
find / \( -perm -4000 -fprintf /root/suid.txt '%#m %u %p\n' \) , \
\( -size +100M -fprintf /root/big.txt '%-10s %p\n' \)

Search for files in your home directory which have been modified in the last twenty-four hours. This command works this way because the time since each file was last modified is divided by 24 hours and any remainder is discarded. That means that to match -mtime 0, a file will have to have a modification in the past which is less than 24 hours ago.
find $HOME -mtime 0

Search for files which have read and write permission for their owner, and group, but which other users can read but not write to (664). Files which meet these criteria but have other permissions bits set (for example if someone can execute the file) will not be matched.
find . -perm 664

Search for files which have read and write permission for their owner and group, and which other users can read, without regard to the presence of any extra permission bits (for example the executable bit). This will match a file which has mode 0777, for example.
find . -perm -664

Search for files which are writable by somebody (their owner, or their group, or anybody else).
find . -perm /222

All three of these commands do the same thing, but the first one uses the octal representation of the file mode, and the other two use the symbolic form. These commands all search for files which are writable by either their owner or their group. The files don't have to be writable by both the owner and group to be matched; either will do.
find . -perm /220
find . -perm /u+w,g+w
find . -perm /u=w,g=w

Both these commands do the same thing; search for files which are writable by both their owner and their group.
find . -perm -220
find . -perm -g+w,u+w

These two commands both search for files that are readable for everybody (-perm -444 or -perm -a+r), have at least on write bit set (-perm /222 or -perm /a+w) but are not executable for anybody (! -perm /111 and ! -perm /a+x respectively)
find . -perm -444 -perm /222 ! -perm /111
find . -perm -a+r -perm /a+w ! -perm /a+x

If you need to run an action against a large quantity of files, an alternative and often much faster method is to execute the command by simply piping find into xargs rather than specifying a find action against each file.

xargs, will bundle up the files and (almost always) run them through a single instance of the called program
find -exec, will run a separate instance of the called program for each file.
Exit Status
find exits with status 0 if all files are processed successfully, greater than 0 if errors occur. This is deliberately a very broad description, but if the return value is non-zero, you should not rely on the correctness of the results of find.

As of findutils-4.2.2, shell metacharacters ('*'. '?' or '[]' for example) used in filename patterns will match a leading '.', because IEEE POSIX interpretation 126 requires this.
$ find . -name *.c -print
find: paths must precede expression
Usage: find [-H] [-L] [-P] [path...] [expression]
This happens because *.c has been expanded by the shell resulting in find actually receiving a command line like this:
find . -name bigram.c code.c frcode.c locate.c -print

That command is of course not going to work. Instead of doing things this way, you should enclose the pattern in quotes:
$ find . -name ´*.c´ -print

delete all directories EXCEPT a particular one

For those of you that just want the straight goods, here’s how we delete everything EXCEPT the ‘backup’ directory:
find . -maxdepth 1 ! -name 'backup' ! -name '.*' | xargs rm -rf



The example in the Smooth Operator boxout creates an m3u playlist listing all ogg files that start 'David Gray' (and all case-permutations)
$ find . -iname david\ gray\*ogg -type f > david_gray.m3u
This will find any files called, in one way or the other, "david gray....ogg".
This is semantically equivalent to:
$ find . -iname david\ gray\*ogg -and -type f > david_gray.m3u
It's equivalent to:
$ find . -iname "david gray*ogg" -and -type f > david_gray.m3u
What if the ogg files themselves mightn't have the artists name in them and are in some subdirectory of one called 'David Gray', how do we find them?
$ find . -ipath \*david\ gray\*ogg -type f > david_gray.m3u
The expression starts with a wildcard because its possible there's more than one subdirectory named 'david gray' that might really be nothing more than symlinks for categorisations.

Here's another example, we list the contents of the humour directory (one line per file) and do a case-insensitive search for .mp3 files with 'yoda' in the name of the file:
$ ls humour -1
Weird Al - Yoda.mp3
werid al - livin' la vida yoda.mp3

$ find -ipath \*humour\*yoda\* -type f
./humour/Weird Al - Yoda.mp3
./humour/werid al - livin' la vida yoda.mp3

2 into 1 does go

As implied in the Smooth Operator boxout, it's possible to have one invocation of find perform more than one task.
To compile two lists, one containing the names of all .php files and the other the names of all .js files use:
$ find ~ -type f \( -name \*.php -fprint php_files , 
                    -name \*.js -fprint javascript_files \)


Suppose you have a playlist file listing all David Gray .ogg files but there are a few albums you don't want included.
You can prevent those albums from going into the playlist by using the -prune action which works by attempting to match the names of directories against the given expression.
This example excludes the Flesh and Lost Songs albums :
$ find   \( -path  ./mp3/David_Gray/Flesh\* -o -path
"./mp3/David_Gray/Lost Songs" \* \) -prune -o -ipath \*david\ gray\*
The first thing you'll notice here is the parentheses are escaped out so BASH doesn't misinterpret them. Notice using -prune takes the form
"don't look for these, look for these other ones instead". ie:
$ find (-path <don't want this> -o -path <don't want this#2>)
\-prune -o -path <global expression for what I do want>
It might take a bit longer to invoke find to use the -prune action: decide exactly what you want to do first. I find using the -prune action saves me time I can use on other tasks.

Fussy Fozzy!

There's a host of other expressions and criteria that can be used with find.
Here is a brief rundown on the ones you'll most likely want to use:
-nouserfile is owned by someone no longer listed in /etc/passwd
-nogroupthe group the file belongs to is no longer listed in /etc/groups
-owner <username>file is owned by specified user.
We'll delve into using these, and others, later on.

Print me the way you want me, baby!

Changing the output information

If you want more than just the names of the files displayed, find's -printf action lets you have just about any type of information displayed. Looking at the man page there is a startling array of options.
These are used the most:
%pfilename, including name(s) of directory the file is in
%mpermissions of file, displayed in octal.
%fdisplays the filename, no directory names are included
%gname of the group the file belongs to.
%hdisplay name of directory file is in, filename isn't included.
%uusername of the owner of the file
As an example:
$ find . -name \*.ogg -printf %f\\n
generates a list of the filenames of all .ogg files in and under the current directory.
The 'double backslash n' is important; '\n' indicates the start of a new line. The single backslash needs to be escaped by another one so the shell doesn't take it as one of its own.

Where to output information?

find has a set of actions that tell it to write the information to any file you wish. These are the -fprint, -fprint0 and -fprintf actions.

$ find . -iname david\ gray\*ogg -type f -fprint david_gray.m3u
is more efficient than
$ find . -iname david\ gray\*ogg -type f > david_gray.m3u


File is an excellent tool for generating reports on basic information regarding files, but what if you want more than just reports? You could just pipe the output to some other utility:
$ find ~/oggs/ -iname \*.mp3 | xargs rm
This isn't all that efficient though.
It is much better to use the -exec action:
$ find ~/oggs/ -iname \*.mp3 -exec rm {} \;
It mightn't read as well, but it does mean the files are immediately deleted once found.
'{}' is a placeholder for the name of the file that has been found and as we want BASH to ignore the semicolon and pass it verbatim to find we have to escape it.
To be cautious, the -ok action can be used instead of -exec. The -ok action means you'll be asked for confirmation before the command is executed.
There are many ways these can be used in 'real life' situations:
If you are locked out from the default Mozilla profile, this will unlock you:
$ find ~/.mozilla -name lock -exec rm {} \;
To compress .log files on an individual basis:
$ find . -name \*.log -exec bzip {} \;
Give user ken ownership of files that aren't owned by any current user:
$ find . -nouser -exec chown ken {} \;
View all .dat files that are in the current directory with vim. Don't search any subdirectories.
$ vim -R `find . -name \*.dat -maxdepth 1`
Look for directories called CVS which are at least four levels below the current directory:
$ find -mindepth 4 -type d -name CVS 

Time waits for no-one

You might want to search for recently created files, or grep through the last 3 days worth of log files.
Find comes into its own here: it can limit the scope of the files found according to timestamps.
Now, suppose you want to see what hidden files in your home directory changed in the last 5 days:
$ find ~ -mtime -5 -name \.\*
If you know something has changed much more recently than that, say in the last 14 minutes, and want to know what it was there's the mmin argument:
$ find ~ -mmin 14 -name \.\*
Be aware that doing a 'ls' will affect the access time-stamps of the files shown by that action. If you do an ls to see what's in a directory and try the above to see what files were accessed in the last 14 minutes all files will be listed by find.
To locate files that have been modified since some arbitrary date use this little trick:
$ touch -d "13 may 2001 17:54:19" date_marker
$ find . -newer date_marker 
To find files created before that date, use the cnewer and negation conditions:
$ find . \! -cnewer date_marker
To find a file which was modified yesterday, but less than 24 hours ago:
$ find . -daystart -atime 1 -maxdepth
The -daystart argument means the day starts at the actual beginning of the day, not 24 hours ago.
This argument has meaning for the -amin, -atime, -cmin, ctime, -mmin and -mtime options.

Empty files

You can find empty files with $ find . -size 0c 
Using the -empty argument is more efficient.
To delete empty files in the current directory:
$ find . -empty -maxdepth 1 -exec rm {} \;

Users & Groupies


To locate files belonging to a certain user:
# find /etc -type f \!  -user root -exec ls -l {} \;
-rw------- 1 lp sys 19731 2002-08-23 15:04 /etc/cups/cupsd.conf
-rw------- 1 lp sys    97 2002-07-26 23:38 /etc/cups/printers.conf
A subset of that same information, without having the cost of an exec:
root@ttyp0[etc]# find /etc -type f \!  -user root \
                 -printf "%h/%f %u\\n"
/etc/cups/cupsd.conf lp
/etc/cups/printers.conf lp
If you know the uid and not the username then use the -uid argument:
$ find /usr/local/htdocs/ -uid 401
-nouser means there is no user in the /etc/passwd file for the files in question.


find can locate files that belong to a specific group - or not, depending on how you use it.
This is especially suited to tracking down files that should belong to the www group but don't:
$ find /www/ilug/htdocs/  -type f \! -group  www
The -nogroup argument means there is no group in the /etc/group file for the files in question.
This may arise if a group is removed from the /etc/group file sometime after it's been used.
To search for files by the numerical group ID use the -gid argument:
$ find -gid 100


If you've ever had one or more shell scripts not work because their execute bits weren't set and want to sort things out for once and for all, then you should like this little example:
knoppix@ttyp1[bin]$ ls -l ~/bin/
total 8
-rwxr-xr-x    1 knoppix  knoppix 21 2004-01-20 21:42 wl
-rw-r--r--    1 knoppix  knoppix 21 2004-01-20 21:47 ww

knoppix@ttyp1[bin]$ find ~/bin/ -maxdepth 1 -perm 644 -type f \
                    -not -name .\* 
Find locates the file that isn't set to execute, as we can see from the output of ls.

Types of files

The '-type' argument obviously specifies what type of file find is to go looking for (remember in Linux absolutely everything is represented as some type of file).
So far I've been using '-type f' which means search for normal files.
If we want to locate directories with '_of_' in their name we'd use:
$ find . -type d -name '*_of_*'
The list generated by this won't include symbolic links to directories.
To get a list including directories and symbolic links:
$ find . \( -type d -or -type l \) -name '*_of_*'
For a complete list of types check the man page.

Limiting by filesytem

As an experiment, get a MS formatted floppy disk and mount it as root:
$ su - 
# mount /floppy
# mount
/dev/sda2 on / type ext2 (rw,errors=remount-ro)
proc on /proc type proc (rw)
devpts on /dev/pts type devpts (rw,gid=5,mode=620)
/dev/fd0 on /floppy type msdos (rw,noexec,nosuid,nodev)
Now try
$ find / -fstype msdos -maxdepth 1 
You should see only /floppy listed.
To get the reverse of this, ie a listing of directories that are not on msdos file-systems, use

$ find / -maxdepth 1 \( -fstype msdos \) -prune -or -print
This is a start on limiting the files found by system type.

No comments:

Post a Comment