HP-UX Admin Man: Find Is Where The Action Is

In this third and final column in the series on the find command, we will look at other action options. In should be written as follows:

find (where) (what) (action)

In previous columns we have discussed the available and options. In all the examples we used the -print option as our action. For example:

find . -name
test -print

This will search the current directory (where to look) for any object named test (what qualifies), and print the pathnames (the action) to those objects.

In addition to -print, there's also a way to execute commands against the objects that are qualified.

The -exec option allows you to specify a command line to be executed for each qualified object found. Looking only at the -exec option makes it easier to figure out how to use it:

-exec command \;

Note that this option takes a command as an argument. The command is essentially "piped" as a text string to a shell for execution, once for each qualified object. Because find might qualify more than one object, you must follow the command with a semicolon so that the shell knows where a command ends. Note that the semicolon must be escaped, since you don't want the shell that find is executed in to interpret it, you want find to "see" the semicolon.

Another very important symbol to the -exec option is braces. You put a set of braces in the command wherever you want find to place the pathname of the currently qualified object.For example, if you used - exec as follows:

find . -name test -exec ls -l {} \;

if two objects were found named test, find would send something like this to a shell for execution:

l./backup/test ;/

You can see why the semicolon is needed to separate the two commands.

Actually, there is no real distinction between and type options to find. It is more in how you use them.

find . -name test -print

prints all objects named test. The man page states that the - print option is always true. The meaning only becomes clear if you write something like this:

find . -name test -print -exec rm {} \;

This command prints qualified pathnames, and because the -print option returns a true, the boolean expression continues, so that the -exec option is also executed, deleting files named test. If there were two objects, one is a file, the other a directory, we might get the following output:

./backup/test
rm: ./backup/test directory
./survey/test

because you cannot rm a directory.

If we reversed the order of the options: we get a better (more correct) result:

find . -name test -exec rm {} \; -print

we get a better (more correct) result:

rm: ./backup/test directory
./survey/test

Now the list only includes the files for which the -exec worked (though we get an error message from rm about those objects it could not remove). We can get rid of the error message with better qualifying:

find . -name test -type f -exec rm {}\; -print

This command now reads something like: "print the names of files deleted that have the name test." If the file cannot be deleted, the pathname will not be -print'd (but you will get an error if the command - exec'd generates one).

An example of when you might want to use - print before - exec (instead of after):

find . -name test -type f -print -exec cat {} \;

To -exec or to not -exec

The -ok option is similar to -exec, except that it prompts and waits for an ok before executing the command:

% find . -name test -ok cat {} \; /td>
< cat="" ...="" ./test="">? y
hi from this dir
< cat="" ...="" ./survey/test="">? n
%

This is a rather useful option if you are issuing a destructive command such as rm, and cannot perfectly qualify objects to be acted on.

Find also makes a nice front end to cpio. You can develop any number of qualifiers, then supply the -cpio option and have those objects written to the supplied device:

find /design -user fredm -cpio /dev/rmt/s0d0

Because we now know that find pipes command lines to a shell for execution, it is rather obvious that something like this:

find . Ðname '*.bak' Ðexec rm {} \;

could result in many processes being created. If we tried this in a korn shell, it might work, but only if there were not that many files found by find. If there were lots of them, we would have probably exceeded maximum string length, and gotten an error.

The solution is a simple one. Pipe the output of find to xargs. The xargscommand understands maximum string length, and makes sure it does not exceed it. Here is an example:

find . Ðname '*.bak' | xargs rm

In this case, xargs makes sure that the rm command is fed as many filenames as possible without exceeding maximum string length.

Many people write a script or program when find would have performed the job easily. In more difficult cases, find does not have enough qualifying options to do exactly what people might need done. Well, if you really understand the discussion above about -exec, you should have noticed that find is extensible. Because all options to find are a boolean expression, you can use - execas a qualifier, supplying a script as the command to be executed. The script must complete with 0 status (successful) if the "test" it contains passes.

As an example, suppose we want to locate all Perl scripts on our system (and assuming they all contain '#!/usr/local/bin/perl' as the first line in the script.) If we were to use this find line:

find / -type f -perm -1 -print

we would end up with all owner executable files: binaries, shell and Perl scripts alike. If all Perl scripts were named ending in .pl life would be good:

find / -name '*.pl' -type f -perm -1 -print

But who names all Perl scripts that way? The solution (obviously) is to extend find to handle Perl scripts. I might do this with a simple shell script as follows (yeah yeah, it could have been a Perl script):

#!/usr/bin/ksh
head -1 $1 | grep -q '#!
*/usr/local/bin/perl'

This script looks at the first line of the file passed to it as a command line argument ($1), to see if it is a Perl script. The grep command returns 0 if a match is found, thus meeting the requirements of find. Assuming the script is in ~/bin named testit, we might try something like:

find / -type f -perm -1 -exec ~/bin/testit {} \; -print

The problem with this is one of speed. We will be opening and looking in many files, so this will take quite some time.

In the meantime, I think I'll go look for my cell phone.

Must Read Articles