## 9.12. Finding Many Things with One Command

Running find is fairly time consuming, and for good reason: it has to read every inode in the directory tree that it's searching. Therefore, combine as many things as you can into a single find command. If you're going to walk the entire tree, you may as well accomplish as much as possible in the process.

Let's work from an example. Assume that you want to write a command (eventually for inclusion in a Chapter 27 shell script) that sets file-access modes correctly. You want to give 771 access to all directories, 600 access for all backup files (*.BAK), 755 access for all shell scripts (*.sh), and 644 access for all text files (*.txt). You can do all this with one command:

>     $ find . \( -type d       -a -exec chmod 771 {} \; \) -o \
>              \( -name "*.BAK" -a -exec chmod 600 {} \; \) -o \
>              \( -name "*.sh"  -a -exec chmod 755 {} \; \) -o \
>              \( -name "*.txt" -a -exec chmod 644 {} \; \)

Why does this work? Remember that -exec is really just another part of the expression; it evaluates to true when the following command is successful. It isn't an independent action that somehow applies to the whole find operation. Therefore, -exec can be mixed freely with -type, -name, and so on.

However, there's another important trick here. Look at the first chunk of the command -- the first statement, that is, between the first pair of `\(` and `\)`. It says, "If this file is a directory and the chmod command executes successfully . . . " Wait. Why doesn't the -exec execute a chmod on every file in the directory to see whether it's successful?

Logical expressions are evaluated from left to right; in any chunk of the expression, evaluation stops once it's clear what the outcome is. Consider the logical expression "`A AND B' is true." If A is false, you know that the result of "`A AND B' is true" will also be false -- so there's no need to look the rest of the statement, B.

So in the previous multilayered expression, when find is looking at a file, it checks whether the file is a directory. If it is, -type d is true, and find evaluates the -exec (changing the file's mode). If the file is not a directory, find knows that the result of the entire statement will be false, so it doesn't bother wasting time with the -exec. find goes on to the next chunk after the OR operator -- because, logically, if one part of an OR expression isn't true, the next part may be -- so evaluation of an OR . . . OR . . . OR . . . expression has to continue until either one chunk is found to be true, or they've all been found to be false. In this case having the directory first is important, so that directories named, for example, blah.BAK don't lose their execute permissions.

Of course, there's no need for the -execs to run the same kind of command. Some could delete files, some could change modes, some could move them to another directory, and so on.

One final point. Although understanding our multilayered find expression was difficult, it really was no different from a "garden variety" command. Think about what the following command means:

>     % find . -name "*.c" -print

There are two operators: -name (which evaluates to true if the file's name ends in .c) and -print (which is always true). The two operators are ANDed together; we could stick a -a between the two without changing the result at all. If -name evaluates to false (i.e., if the file's name doesn't end in .c), find knows that the entire expression will be false. So it doesn't bother with -print. But if -name evaluates to true, find evaluates -print -- which, as a side effect, prints the name.

As we said in Section 9.6, find's business is evaluating expressions -- not locating files. Yes, find certainly locates files; but that's really just a side effect. For me, understanding this point was the conceptual breakthrough that made find much more useful.

-- ML

Copyright © 2003 O'Reilly & Associates. All rights reserved.