Finding Things in Unix

One of the most useful utilities to be found on any Unix system is the find command.

In the next two articles, I'd like to work you through the syntax of this command and provide you with some practical examples of its usage.

The command itself has a very simple syntax:

find where_to_search expressions

The expressions are the part that can look confusing the first few times
you use find. They are also the part that can vary from Unix system to
Unix system, so you may want to take a quick peek at find's manpage if
you find yourself on a new system. The most common expressions that
you can use on your FreeBSD system are as follows:

-name

must be quoted when using wildcards

-type

e.g. f=file d=directory l=link

-user

name or UID

-group

name or GID

-perm

specify permissions

-size

rounded up to next 512 byte block or use c to specify bytes

-atime

last time file was read

-ctime

last time file's owner or permissions were changed

-mtime

last time file was modified

-newer

find files newer than given file

-delete

remove files found

-ls

gives same output as ls -dgils

-print

displays results of find command

-exec command {} \;

to execute a command; note the required syntax

-ok

use instead of exec to be prompted before command is executed

-depth

starts at lowest level in directory tree rather than root of given directory

-prune

used when you want to exclude certain subdirectories

I'll be giving examples that show how to use and combine these expressions.
Before doing that, what can find be used for? If you use the whatis
command to see what it does, the answer may surprise you:

whatis find
find(1) - walk a file hierarchy

In a nutshell, find is meant to recursively search directories to
find any files that meet your specified expressions. This may not seem
like such a big deal, but there aren't that many Unix utilities that
can "walk" through a directory and all of its subdirectories. This ability
proves quite useful, as not only can you find files, but you can do
something with them as you find them.

Let's start with some simple examples and then work our way towards some more seemingly-complicated expressions. The simplest find you can do is
to simply type this:

find . -print

Since the "." represents your current directory, this find command will
find all of the files in your current directory and all of its
subdirectories, and then print the results to your screen.

On your FreeBSD system, -print is assumed if you forget to type it, so
this command will also give the same result:

find .

However, it's a good idea to get in the habit of using -print, in case you ever need to do a find on a system that does not assume this action.

To find all of the files in your home directory, you should first make sure
you are in your home directory, then repeat that find command, like so:

cd
find . -print

The cd command will always take you to your home directory. Since the
find command can be used to do powerful things, it is always a good idea
to first cd to the directory structure in which you wish to work. For the rest
of this article, I will assume that you are in your home directory, so you
won't inadvertantly affect any files on your FreeBSD system that don't
reside in your home directory.

The above examples demonstrated how easy it is to use find, but usually
you are looking for something specific when you invoke the find command.
This is where the other expressions come into play. Let's try to find
a file with a specific name:

touch file1
find . -name file1 -print
./file1

Let's pick apart what I just did for a moment. I created an empty file named
file1 using the touch command. I then told find to search my current
directory (".") for a file named (-name) file1 and to print the results of
the search to my screen. Also, I can tell that I only have one file named
file1 in my home directory and all of its subdirectories, as only one result
was displayed.

Often, when you need to use the find command, you are looking for more than
one file. For example, you may want to find all of the files with a
certain extension. I tend to download a lot of .pdf files and don't always
remember to keep them in the same place. Occasionally, I like to collect
them and put them in a directory I've created named pdfs. When this urge
strikes, I can use the following find command to search my home
directory and its subdirectories for all .pdf files:

It looks like I've been pretty good lately, as I only have one .pdf file
that is not in my pdfs directory.

You'll note that in order to get this find command to work, I had to
"quote" the "*" wildcard by typing "*.pdf" instead of just *.pdf. There
are two other ways to quote, so the following two commands will yield the same
results:

find . -name \*.pdf -print
find . -name '*.pdf' -print

Let's add to that original command and see how the output changes. What if
I was only interested in seeing which .pdf files were not in the pdfs
directory? Let's repeat that find command, but pipe the results to grep so only that one file will be displayed:

find . -name "*.pdf" -print | grep -v "^\./pdfs/"
./2000_ports.pdf

Well, that command worked, but that syntax looks pretty scary; we better
pick it apart. Whenever you use grep -v, you are creating a reverse
filter, meaning that you want it to show the opposite of what follows. In
my case, I'm not interested in the files that reside in the ./pdfs/
directory; I want to find the files that aren't, so I used the reverse
filter. You'll note that I also "quoted" the whole expression. I also
added a bit extra, that ^\ stuff. The ^ tells grep to only search
the very beginning of a line for my expression. The \ is an extra quote so that
grep does not interpret the . as a special character. The whole thing put
together told grep to just show me the files that don't live in the
./pdfs/ directory, so I received the desired output.

Brave enough to try something even more useful but complicated-looking?
Let's get find to not only find this file, but move it to the correct
directory using the following command:

So it worked. Let's see why. Once grep finished filtering the find
output, we piped that result to the xargs command to finish the job for
us. The J switch tells xargs to take all of the files it receives and
assume that the file you specify with the command is to be the destination.
For example, before I ran the find command, I had no idea how many files
needed to be moved. There may have been one, or there may have been several. I
needed to let xargs know that regardless of how many files were found, I
want them all moved and I want them all moved to the pdfs directory. That bit
of magic is the job of the J switch. To get the J switch to work
properly, I also defined a character (X) and put that character on either side
of the mv command.

Remember that Unix filenames don't necessarily have extensions, so you may
want to search for a more complicated pattern. Let's say I want to find
any files that have "bsd" somewhere in their name. I would do this
command:

We can also find a file by more than just its name. For example, to find
all the files that you have not read in more than (+) 30 days:

find . -atime +30 -print

To see files you haven't modified, use -mtime instead, and to see files you haven't changed the owner or permissions of, use -ctime. The number
after the + indicates how many days or 24-hour periods. To see which
files were modified today, try:

find . -mtime -1 -print

This will show what files were modified during the last 24 hours. Note
that this time you should use the -, as you want to find the files from
less than one day ago.

The other switch that deals with time is the -newer switch. The three
time switches all use 24-hour periods. If you would like to be a bit
more granular in your time than that, the -newer switch will compare a
file's access, modification, and change times to within a minute. For
example, to see if any of your "dot" files were changed since you last
changed your .cshrc file, you could execute this command:

find . -type f -name ".*" -newer .cshrc -print

You'll note that I've included some other new switches in this command. I
specified a "type" of -f for files, as I don't want to see any
directories, just files. I told the -name switch that I was
interested in seeing files that start with a ".". Finally, I used the
-newer switch to indicate that I was interested in the files that were
modified since I last modified my .cshrc file.

Since I've started to combine switches that indicate which files I'm
interested in finding, I should mention that all switches are logically
"anded" unless you use the -o or logical "or". Since the switches are
logically anded, I really told the find utility that I was only
interested in files that were of a certain type and had a certain name
and were newer than my .cshrc file.

Let's look at an example that shows the difference between a logical "and" and a
logical "or". If I wanted to see all of the files in my home directory that had
not been accessed in the last seven days "and" are larger than 10 Mb, I would
use this command:

find . -atime +7 -size +20480 -print

However, if I wanted to see any files that either had not been accessed in
the last seven days "or" that were over 10 MB in size, I would use this
command instead:

find . -atime +7 -o -size +20480 -print

You'll note that I had to do some math to come up with the number to give
to the -size expression, since -size is looking for the number of 512-byte blocks. However, I could have used the expr command to do the math
for me, like so:

find . -atime +7 -o -size +`expr 10 \* 1024 \* 2` -print

Note that in this example, everything between the backquotes (the ` on the far left of your
keyboard) is what will do the required math. We still need
the + in front of the first back quote, as we want to see files greater than
10 MB. You could also test what the results of the math will be by adding the
echo command to the beginning of the command:

It is a good idea to echo complex commands first, to ensure that the
stuff you've quoted will do what you expect before asking the find command to execute it.

That should get you started for this week. In next week's article, I'll
continue through the rest of the expressions and give some more practical
examples for using the find command.

Dru Lavigne
is a network and systems administrator, IT instructor, author and international speaker. She has over a decade of experience administering and teaching Netware, Microsoft, Cisco, Checkpoint, SCO, Solaris, Linux, and BSD systems. A prolific author, she pens the popular FreeBSD Basics column for O'Reilly and is author of BSD Hacks and The Best of FreeBSD Basics.