Guide to Linux Find Command Mastery

A quick introduction to the most powerful—as well as confusing—aspects of this ubiquitous command
By Sheryl Calish
Published July 2008

The Linux find command is simultaneously one of the most useful and confounding of all Linux commands. It is difficult because its syntax varies from the standard syntax of other Linux commands. It is powerful, however, because it allows you to find files by filename, by file type, by user, and even by time stamp. With the find command, not only are you able to locate files with any combination of these attributes, but you can also perform actions on the files it finds.

The purpose of this article is to simplify the task of learning and using the find command by giving you an overview of its purpose and potential. At the same time, it will provide a basic tutorial and reference for some of the most powerful but confusing aspects of the find command.

[Note: The version of find used for this article is the GNU version, so some details may vary for other versions of find.]

Basic Format

To begin, let’s look at the basic structure of the find command



find   start_directory  test  options   criteria_to_match
action_to_perform_on_results

In the following command, find will start looking in the current directory, denoted by the “.”, for any file with the “java” extension in its name:

find . -name "*.java"

Here’s an abbreviated listing of what it found:



find . -name  "*.java"
./REGEXPvalidate/src/oracle/otnsamples/plsql/ConnectionManager.java
./REGEXPvalidate/src/oracle/otnsamples/plsql/DBManager.java
..

[Note: If you cut and paste from this article to run the find command, you may need to replace the double quotes (“”) using your own keyboard for proper results.]

The following command will do the same thing. In either case, you need to escape the wildcard character to be sure it passes to the find command and is not interpreted by the shell. So, put your search string in quotes, or precede it with a backslash:

find . -name \*.java

Although all arguments to find are optional, the search will begin by default in the current directory if you do not specify where to begin searching. If you do not specify a test condition, an option, or a value to be matched your results will be either incomplete or indiscriminating. 

Running the following three find commands will all yield the same results—a full listing of all files in the current directory and all subdirectories including hidden files:



find 
find .
find . -print

This is similar to running an ls command with the -la options. If you want the output of the above commands to contain the full pathnames, perhaps for a backup, you would need to specify the full path for the starting directory:



find /home/bluher -name \*.java
/home/bluher/plsql/REGEXPvalidate/src/oracle/otnsamples/plsql/ConnectionManager.java
/home/bluher/plsql/REGEXPvalidate/src/oracle/otnsamples/plsql/DBManager.java/
...

You can also specify more than one starting directory in a search string. If run as a user with appropriate privileges, the following command will descend into the /usr, /home, and then /tmp directories to look for all jar files:

find /usr /home /tmp -name "*.jar"

If you don’t have the appropriate permissions, however, you will generate error messages as you begin to look through many system directories. The following is an example:

find: /tmp/orbit-root: Permission denied

You can avoid a cluttered output by appending your search string as follows:

find /usr /home /tmp -name "*.jar" 2>/dev/null

This sends all error messages to the null file, thereby providing cleaner output.

By default, find is case sensitive. For a case-insensitive find, substitute the -iname test for the -name test.



find downloads  -iname "*.gif"
downloads/.xvpics/Calendar05_enlarged.gif
downloads/lcmgcfexsmall.GIF

You can also search for files by types other than filename. For instance, you can find all the subdirectories in a directory with the following command:

find . -type d

You can find all the symbolic links in your /usr directory with the following command:

find /usr -type l

This will probably yield a list of more than 3,000 links. Either of the following commands, run with root privileges, will yield a list of links in the /usr directory and the file to which it points:




# find /usr/bin  -type l  -name "z*" -exec ls  -l {} \;
lrwxrwxrwx 1 root  root 8 Dec 12 23:17 /usr/bin/zsh -> /bin/zsh
lrwxrwxrwx 1 root  root 5 Dec 12 23:17 /usr/bin/zless -> zmore
lrwxrwxrwx 1 root  root 9 Dec 12 23:17 /usr/bin/zcat -> /bin/zcat
find /usr/bin -type  l  -name "z*" -ls

The second, shorter command, however, will yield a long file list with directory and inode information as well. We will discuss the use of the -exec and -ls actions later in this article.

Other filetypes that find can locate include

• b—block (buffered) special 
• c—character (unbuffered) special 
• p—named pipe (FIFO) 
• s—socket

Using root as the starting point for a find command can slow down your system significantly. If you really must run such a command, you might run it during low-use time or overnight. You can redirect the output to a file using the following syntax:

find / -print > masterfilelist.out

If you find you have erroneously entered a find command that produces copious unwanted output, just interrupt the command by pressing CTRL-C, which stops the most recently executed command.

On an enterprise network with multiple file systems, it is especially good practice to restrict the files that find looks for. Use as many of the options and tests as necessary to reduce the load on your system. Two of the most useful options for this purpose are -xdev and -mount. They narrow the search by preventing find from descending into directories on other file systems such as MS-DOS, CD-ROM, or AFS. This restricts the search to the same type of file system as the startdirectory.

Users on a dual boot system can play with these options if the mount command is run. Assuming that a Windows partition is involved, you could mount it with something like the following command:

mount -t vfat /dev/sda1 /mnt/msdos

The actual command that you use will depend on how your system is set up. You can verify that the partition was mounted by running df or by executing the following command:

find /mnt/msdos -name "*.txt" 2> /dev/null

You should see a long list of files on the MS Windows partition. Now run the following command with either the -mount or -xdev option:




find / -name  "*.txt" -mount 2> /dev/null
or

find / -name  "*.txt" -xdev 2> /dev/null

Another possibility is to explicitly tell find which file system to look in using the -fstype test, as in this example:

find / -name "*.txt" -fstype vfat 2> /dev/null

Finding Time

The find command has several options that are used to search for files based on your system’s time stamps. These time stamps include

  • mtime the time that the contents of a file were last modified
  • atime—the time that a file was read or accessed
  • ctime—the time that a file’s status was changed

While the meaning of mtime and atime is pretty self-evident, ctime requires more explanation. Because the inode maintains metadata on each file, the inode data will change if the metadata related to the file is changed. This could be caused by a range of actions, including the creation of a symbolic link to the file, changing the permissions on a file, or moving the file. Because the contents of the file are not being read or modified in these cases, themtime and atime will not change, but the ctime will change. 

Each of these time options needs to be used with a value n , which is specified as -n, n, or +n.

  • -n returns less than n 
  • +n returns greater than n 
  • n , by itself,returns exactly n matches

Let’s look at some examples to make this clearer. The following command will find all the files modified within the last hour:




find . -mtime -1
./plsql/FORALLSample
./plsql/RegExpDNASample
/plsql/RegExpSample

Running the same command with 1 instead of -1 finds all the files that were modified exactly one hour ago:

find . -mtime 1

The above command may not yield any results, because it asks for an exact match. The following command looks for the files that were modified more than an hour ago:

find . -mtime +1

By default, -mtime, -atime, and -ctime refer to the last 24 hours. If, however, they are preceded by the daystart option, the 24-hour period will begin with the start of the current day. You can also look for time stamps that have changed in less than an hour using mmin, amin, and cmin.

If you run the following immediately after logging into your account, you will find all files read less than 1 minute ago:



find . -amin -1
./.bashrc
/.bash_history
./.xauthj5FCx1

It should be noted that locating a file with the find command itself changes that file’s access time as part of its metadata.

You can also find files that have been modified or accessed in comparison to a specific file with the options -newer, -anewer, and –cnewer. This is similar to -mtime, -atime, and –ctime. 

  • -newer refers to files whose contents have been modified more recently 
  • -anewer refers to files that have been read more recently 
  • -cnewer refers to files whose status has changed more recently

To find all the files in your home directory that have been edited in some way since the last tar file, use this command:

find . -newer backup.tar.gz

Finding Files by Size

The -size option finds files that meet the size criteria specified. To find all user files larger than 5MB, use



find / -size  +5000000c 2> /dev/null
/var/log/lastlog
/var/log/cups/access_log.4
/var/spool/mail/bluher

The “c” at the end reports our results in bytes. By default, find reports size as the number of 512-byte blocks. We could also see the results as the number of kilobytes if we replace the “c” with a “k” or as the number of two-byte words if we use a “w”.

The -size option is frequently used to search for all zero-byte files and move them to the /tmp/zerobyte folder. The following command does just that:

find test -type f -size 0 -exec mv {} /tmp/zerobyte \;

The -exec action allows find to perform any shell command on the files it encounters. You will see many more examples of its use later in this article. The curly brackets allow each of the empty files to be moved.

The option -empty can also be used to find empty files:




find test -empty        
test/foo
test/test

Finding by Permission and Ownership

The find command can be indispensable for monitoring the security of your system. You can look for files with wide open permissions using symbolic or octal notation as follows:



find . -type f  -perm a=rwx -exec ls -l {} \;
or

find . -type f  -perm 777 -exec ls -l {} \;
-rwxrwxrwx 1 bluher  users 0 May 24 14:14 ./test.txt

In the above and the following commands in this section, we are using the -exec ls -l action so you can see the actual permissions of the files returned. The command will find files that are writable by both the “other” and the group:




find plsql -type f  -perm -ug=rw -exec ls -l {} \; 2>/dev/null
or

find plsql -type f  -perm -220 -exec ls -l {} \; 2>/dev/null 
-rw-rw-rw- 1 bluher users 4303  Jun  7   2004 plsql/FORALLSample/doc/otn_new.css
-rw-rw-rw- 1 bluher users 10286 Jan  12  2005  plsql/FORALLSample/doc/readme.html
-rw-rw-rw- 1 bluher users 22647 Jan  12  2005  plsql/FORALLSample/src/config.sql
..

This next command will find files that are writable by the user, the group, or both:




find plsql -type f  -perm /ug=rw -exec ls -l {} \; 2>/dev/null, or,
find plsql -type f  -perm /220 -exec ls -l {} \; 2>/dev/null 
-rw-r--r-- 1 bluher users 21473  May  3 16:02 plsql/regexpvalidate.zip
-rw-rw-rw- 1 bluher users 4303  Jun  7   2004 plsql/FORALLSample/doc/otn_new.css
-rw-rw-rw- 1 bluher users 10286 Jan  12  2005  plsql/FORALLSample/doc/readme.html
-rw-rw-rw- 1 bluher users 22647 Jan  12  2005  plsql/FORALLSample/src/config.sql

You may see the following command cited on the Web and in older manuals:

find . -perm +220 -exec ls -l {} \; 2> /dev/null

The + symbol does the same as the / symbol, although it is now deprecated in newer versions of GNU findutils.

To find all files on your system that are world writable, use the following command:



find / -wholename  '/proc' -prune  -o  -type f -perm -0002 -exec ls -l {} \;
-rw-rw-rw- 1 bluher users 4303  Jun  7   2004/home/bluher/plsql/FORALLSample/doc/otn_new.css
-rw-rw-rw- 1 bluher users 10286 Jan  12  2005  /home/bluher/plsql/FORALLSample/doc/readme.html
...

The fourth permission will be discussed in just a bit, but the “2” in the last field is the “other” field in the file permissions, also known as the write bit. We used a dash before the permission mode of 0002 to indicate that we want to see files with the write permission set for others, regardless of what other permissions are set.

The above command also introduces three new concepts. Using -wholename tests for the file pattern “/proc”, if that pattern is found, -prune  prevents find from descending into that directory. The Boolean “-o” enables find to process the rest of the command for other directories. As there is an implicit and operator (-a)  that is assumed between each expression, expressions following the and will not be evaluated if the expression on the left evaluates to false; hence the need for the -o operator. The Boolean -not, !, is also supported by find, as is the use of parentheses to force precedence.

System administrators frequently use find to search for regular files of specific users or groups using the name or ID of that user or group:

[root] $ find / -type f -user bluher -exec ls -ls {} \;

Here is a severely abbreviated sample of the output of such a command:



4 -rw-r--r-- 1 bluher users 48  May  1 03:09  /home/bluher/public_html/.directory
4 -rw-r--r-- 1 bluher users 925  May  1 03:09 /home/bluher/.profile

You can also use find to look for a file by group:




[root] $ find /  -type f -group users
find / -type d -gid  100

This command will yield a list of directories owned by group ID 100. To find the appropriate uid or gid,  you can run the more or cat command on the /etc/passwd or /etc/group file.

Beyond finding files of specific known users and groups, you may also find it useful to look for files lacking either of these. This next command identifies files that do not have a listing in the /etc/passwd or /etc/group file:

find / -nouser -o -nogroup

The above command might not actually yield results on your system. It could be used, however, to identify files without a user or group, perhaps after moving files around.

OK, now we can address that extra leading permission mentioned at the beginning of this section.

The SGID and SUID are special access right flags that can be assigned to files and directories on a UNIX-based operating system. They are set to allow ordinary users on a computer system access to execute binary executables with temporarily elevated privileges.




find /  \( -perm -2000 -o -perm -4000 \) -ls
167901   12 -rwsr-xr-x   1 root     root         9340 Jun 16  2006 /usr/bin/rsh
167334   12 -rwxr-sr-x   1 root     tty         10532 May  4  2007 /usr/bin/wall

In the above command, you can see the use of escaped parentheses. You can also see the differences in permissions. The first file has the SGID permission set, and the second file has the SUID permission set. The final action in the above command works like find with the -exec ls -dils action.

Controlling find

Unlike many commands in Linux, find does not require the -r or -R option in order to descend into the subdirectories. It does this by default. However, you may want to limit this behavior at times. For that reason, the options -depth, -maxdepth, and -mindepth and the action -prune may come in handy.

We have already seen how useful -prune can be, so let’s take a look at the -depth, -maxdepth, and -mindepth options.

The -maxdepth and -mindepth options allow you to specify how far down the directory tree you want find to search. If you want find to look in just one level of the directory, you would use the maxdepth option.

You can see the effects of -maxdepth by running the following command to look for log files in the top three levels of the directory tree. This will produce considerably less output than when run without it.

find / -maxdepth 3 -name "*log"

You can also tell find to look in directories at least three levels down the directory tree:

find / -mindepth 3 -name "*log"

The -depth option ensures that a directory is evaluated before its contents are evaluated. The following command provides an example:



find -name "*test*" -depth
./test/test
./test
./localbin/test
./localbin/test_shell_var
./localbin/test.txt
./test2/test/test
./test2/test
./test2

The World of find

We have looked at some of the more useful and somewhat obscure abilities of the find command, but there are additional tasks that find can perform. For instance, there are options that make find compatible with older UNIX versions and other operating systems and that allow you to perform actions such as printing output to multiple files. After reading this article, you now have the background to understand the man page on find, and I encourage you to explore this powerful and useful tool.

Sheryl Calish is an IT consultant specializing in Linux. Previously with Blue Heron Consulting, she is currently working as director of IT for EquityBuild.