Running find is fairly time-consuming, and for good reason: it has to read every inode (1.22) in the directory tree that it's searching. Therefore, it's a good idea to 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 shell script) that sets file access modes (22.2) correctly. You want to change all directories to 771 access, 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. (Also, see article 17.11.)
However, there's another important trick here. Look at the first chunk of the command. 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, trying to see whether or not it's successful?
Logical expressions are evaluated from left to right; and, 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 at B.
So in the multi-layered expression above,
when find is looking at a file, it checks whether or not
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.
And, 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 multi-layered 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 article 17.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.
-