91/12/08 THE QNX 4 FIND UTILITY (POSIX 1003.2 + EXTENSIONS) - TUTORIAL --------------------------------------------------------------- *** This document Copyright (c) 1991, 1995 by QNX Software Systems Ltd. *** The document may be reproduced and distributed freely on the *** condition that its text is unmodified and that this notice is *** retained. If you would like to see modifications made, suggestions *** may be addressed to the author via QUICS (613) 591-0934 (userid eric), *** or by email to eric@qnx.com). The find utility is very powerful. Once understood thoroughly it can be hard to live without. However, most people (myself included) find the syntax to be awkward. For that reason, I've written this little tutorial on find in the hopes that it will answer some questions before you have to ask, going into a little more depth than is found in the utility manual. Most features of find, in particular the most basic and most esoteric of them, are covered in detail in this tutorial. There are some features that are straightforward to use with a basic understanding of find. I don't cover these here. So work through the tutorial, try things out, then read the documentation in the utility manual to pick up the bits I don't touch on here. Note that in the many examples there are a lot of \s used. Find itself does not use \ characters on its command line. These are being used to keep the shell from acting on characters that it would otherwise regard as special. They are stripped by the shell before the find utility gets them. THE BASICS ------------ Syntax: find path... [expression] Find always requires one or more file names to be supplied on the command line. For each of these files it evaluates an expression (this is where the 'work' is done - we will get back to that). If the file was a directory, it will then evaluate the expression for every file (or directory) that lies within that directory. And so on recursively for every directory that it encounters. When no expression is supplied e.g. $ find . the find utility uses its default expression, which is -print. i.e. $ find . and $ find . -print are identical. The -print expression, when evaluated, causes the filename currently being evaluated to be printed to standard output. So, our command $ find . will print to standard output the name of every file in and under our current working directory. The output might look something like this: $ find . . ./.profile ./.lastlogin ./letter1 ./bunk ./stuff ./stuff/note1 ./stuff/note2 ./stuff/morestuff/myfile ./.newsrc ./techdata ./work ./work/test.c ./work/test.h ./work/test ./work/hello.c ./work/hello ./.elm ./mydir ./.plan ./.project ./haha $ _ Note that the output is unsorted. Find is just applying the expression (-print) to each file as it encounters it, walking through the directory tree under the current working directory. Notice that the first thing printed was '.' itself. The expression is applied to each file or directory on the command line as well as any files and directories that lie beneath them. In our example, find applied the expression (-print) to . then went on to process the files and directories that lay beneath . (because it saw that . was a directory). As I have implied, find does not require that the files supplied on the command line be directories. If an item is not a directory, it will evaluate the expression for that file and continue to the next file (if there is one) on the command line. For instance, given our current working directory from the example above, we could do this: $ find * letter1 bunk stuff stuff/note1 stuff/note2 stuff/morestuff/myfile techdata work work/test.c work/test.h work/test work/hello.c work/hello mydir haha $ _ In this example, the shell replaced the * with the files and directories matching the pattern '*'. So what the find utility saw was this: find letter1 bunk stuff techdata mydir haha Find evaluated its default expression (-print) for each of these files in turn. One file (stuff) was a directory, so find recursed into it and evaluated the expression for each file beneath it. Note that all the .(something) files are missing. This is because the shell treats files starting with . as special and does not match them with the * wildcard. The find utility itself does not treat any filename pattern (such as files starting with .) as being special. SIMPLE EXPRESSIONS AND THE -a (AND) OPERATOR -------------------------------------------- The simplest expressions consist of a single 'primary' expression. A primary expression is a fundamental built-in operation that the find utility can evaluate. Every primary expression evaluates to TRUE or FALSE. I will henceforth refer to primary expressions simply as 'primaries'. All primaries and operators start with a - (minus) character. (With the exception of ! (not) and parentheses ( and ) which I will discuss later.) The primary we were looking at before: -print happens to _always_ evaluate to TRUE (by definition). In the examples we have been looking at so far, the fact that it always returns TRUE has not mattered. The TRUE/FALSE evaluation becomes significant in the case where multiple expressions are joined with operators, or when you are interested in the exit status of the find utility. (Exit status will be discusses later.) In this section, I will introduce an operator (AND), and another primary expression (-name) that does not always evaluate to TRUE. The -name Primary: The -name primary is always used with an accompanying pattern. A typical example might look like this: -name \*.c #match all files ending in .c The -name primary evaluates to TRUE if the filename matches the pattern, and evaluates to FALSE if the filename does not match the pattern. Only the basename of the filename is considered for the pattern matching. That is to say, if we were to use -name \*dir\* (match all files containing 'dir'), the primary would return FALSE for the file mydirectory/myfile, but TRUE for the directory mydirectory itself or for the file mydirectory/dir2. The -a (AND) Operator: The AND operator is the most frequently used operator. It is represented by -a and is used to join expressions in this manner: (expression1) -a (expression2) The new expression formed by this (something AND something) evaluates to TRUE if both expression1 and expression2 evaluate to TRUE for the current file, but will evaluate to FALSE if either expression1 or expression2 evaluates to FALSE. If expresssion1 evaluates to FALSE, the expression as a whole is FALSE no matter what expression2 might evaluate to. For this reason, find will not evaluate expression2 at all if expression1 evaluates to FALSE. IT IS CRITICAL TO UNDERSTAND THIS AS IT IS FUNDAMENTAL TO THE OPERATION OF THE FIND UTILITY. Let's look at an example, again working with the files in our current working directory from our earlier examples. Let's say we wanted to find every file ending with '.c' that lies under our current working directory. This is how we would do it: $ find . -name \*.c -a -print ./work/test.c ./work/hello.c $ _ Note that the * in the pattern is escaped with a \ to prevent the shell from attempting to replace it with all files in the current directory matching *.c! In the example above, every time the expression -name \*.c -a -print was applied to a file that did not match *.c, find did not evaluate the right hand side of the expression, because the expression as a whole cannot possibly be true when the left hand side (-name \*.c) is false. So for these files no output was printed. For all the files that did end in .c, the left hand side was true, forcing the right hand side to be evaluated. The evaluation of -print caused the filename to be printed to standard output, and the expression as a whole to evaluate to true. Thus, the expression as a whole was FALSE for those files not ending in .c and TRUE for those ending in .c, with the filenames being printed out by the evaluation of -print in the latter case. Exit Status ----------- Now is probably a good time to mention why we would care about the evaluation (true or false) of the expression as a whole. The answer is that it can affect the exit status of the find utility. If the expression as a whole is FALSE for _EVERY_ file that find evaluates, find will exit with a non-zero exit status. Basically it means 'I could not find any files that met the criteria laid out by the expression supplied'. In all our examples above, the exit code would have been 0 (success). If we modified the example to look for a pattern that none of the files match: $ find . -name \*.F -a -print $ echo $? 1 $ _ the exit status would be 1 (failure) - there were no files matching the pattern found under the current working directory. A couple shortcuts ------------------ Since the -a operator is so common, find allows you to omit the explicit -a. Find will assume an 'and' operation from the juxtaposition of two expressions. For example, -name \*.c -a -print is the same as -name \*.c -print The other shortcut involves find's 'default' action. There are only a handfull of primaries that do anything other than simply evaluate to a TRUE or FALSE. These are -print (which you are already familiar with), -ls, -exec, -ok and -spawn (all of which I will get to soon). If you didn't specify any of these find would just churn away producing no output, doing nothing. Since this would be rather pointless, if the find utility sees that you have not specified one of these 'action' primaries anywhere in the expression, it will do a -print implicitly when your expression as a whole evaluates TRUE. So, to reduce our example even further, we can replace find -name \*.c -print with find -name \*.c Both forms will produce the same results. PRIMARIES THAT DO STUFF: -PRINT, -LS, -EXEC, -OK and -SPAWN ----------------------------------------------------------- There are some primaries that can perform actions other than just testing a condition and evaluating to TRUE or FALSE. I call these 'action' primaries, the most common of which is -print. Note that all of these action primaries operate on the whole filename, not just the basename portion of it. -print is the simplest of this class of primaries. It writes the filename, followed by a newline, to standard output. It always evaluates to TRUE, so if it is evaluated and expression 'and'ed to the right of it will also be evaluated. -ls Like -print, -ls always evaluates true. It writes to standard output the filename and information on that file in 'ls -l' format (see the documentation for the ls utility). This is primarily intended for interactive use where you want to see the file size, permissions, links, time etc. The -exec, -ok and -spawn primaries all invoke another program when evaluated. Each is expressed in the form similar to -exec program_name [arguments...] \; Where the ; indicates the end of the primary. Anything following the ; is another primary or operator, not an additional argument to program_name. In each of these primaries, every pair of braces {} will be replaced with the name of the file being evaluated. (Note that there are restrictions on the {} replacement with the -spawn form - details below.) For example: find mydir -name \*.c -exec echo {} \; will produce the same output as the more familiar find mydir -name \*.c -print except that it will do it slower because of the extra overhead in starting up another program to print out the line. All primaries that invoke another program evaluate to TRUE if they successfully start the program and the program comes back with a 0 exit status; and evaluate to FALSE if they cannot execute the program or the program comes back with a non-zero exit status. The differences between -exec, -ok and -spawn are as follows: -exec is the standard way of invoking a program from find. It will replace {} with the filename anywhere within the program name and arguments (does not have to stand alone, as is required by -spawn), and starts a shell to process the resulting command line (thus shell commands are allowed, and shell files may be specified as the executable). -ok Prompts you via stderr as to whether you wish to execute the named program on the file being acted on. Reads a response via stdin. This is generally used when you are invoking a destructive program (e.g. rm) and aren't entirely sure what files find is going to match. If the response is affirmative, the program is executed identically to -exec. -spawn spawns the named executable directly, without starting a shell to execute the command. Therefore -spawn does not support shell scripts or shell commands. There is a further restriction in that the braces {} must stand alone to be replaced by the filename. e.g. given that find is evaluating the file 'poot.o', -spawn rm {} \; #will do 'rm poot.o' but -spawn rm ./{} \; #will do 'rm ./{}' because the {} will not be replaced with the filename in the latter case. The benefit of using -spawn is that it is considerably faster than -exec. Some examples: (try them out so you see the differences) $ find . -name \*.[ch] -exec echo gook{}wook {} \; $ find . -name \*.[ch] -spawn echo gook{}wook {} \; #behaves differently! $ find . -name \*.[ch] -ok echo gook{}wook {} \; I will use these primaries in more examples to follow - the ones above are just 'safe' ones for you can try that demonstrate the different behaviours of the three primaries. MORE PRIMARIES -------------- The primaries are all documented under 'find' in the QNX Utilities the QNX 4 Utilities manual. However, there are a few that warrant some extra examples: - primaries involving numbers (file sizes, times, numbers of extents, etc.) - primaries evaluating the permissions of a file (-perm) There is a convention for all primaries that accept numbers whereby any number preceded by a plus sign means 'evaluate to true for all numbers greater than this number', and any number preceded by a minus sign means 'evaluate to true for all numbers less than this number', while numbers with neither a plus sign nor minus sign means 'evaluate to true for all numbers exactly equal to this number'. Numbers should be expressed as decimal integers. The primaries that take a number (n) are: -level n True if find is n levels down in the directory tree. Any file or directory (itself) named on the command line is level 0. The first-level contents of a directory named on the command line is level 1 etc. -links n Evaluate the number of links to the file. -size n[c] Evaluate the file size in blocks (i.e. bytes divided by 512 then rounded up to the next integer). If n is followed by the letter c (e.g. 16384c) the size will be evaluated in bytes instead of blocks - no division or rounding will be done. -atime n Evaluate the file access time of the file subtracted from the time that the find utility started running. True if this value is between n-1 and n multiples of 24 hours. In English: true if the file was last accessed sometime during the period between n-1 days ago and n days ago. If n is zero, therefore, the primary will evaluate true the the file was last accessed sometime during the last 24 hours. If n is 1, the primary will be true if the file was last accessed between 48 and 24 hours ago, etc. If you enter the number with a plus sign, the files whose last access date was longer ago than that many days will be found. +0 finds all files. If you enter the number with a minus sign then those files that have been accessed more recently than that date will be found. -1 finds those files accessed in the last 24 hours. -0 finds no files unless some files have a timestamp that is in the future relative to the current system time. -mtime n Like -atime, except the file modification time is evaluated. -ctime n Like -atime, except the file status change time is evaluated. -ftime n Like -atime, except the file creation time is evaluated. -extents n Evaluate the number of extents for this file in the filesystem. A large number of extents indicates a fragmented file. Some examples: For each file in /tmp that has not been accessed for seven or more 24-hour periods, display info on the file in ls -l format, ask if it should be removed, and if the response is affirmative, remove it: find /tmp -atime +7 -ls -ok rm -f {} \; Show all files in the current working directory that have been created within the last day: find . -ftime 0 Find all files with more than 10 extents, and display information on those files in ls -l format: find / -extents +10 -ls Find all files in /bin and /usr/bin which are smaller than 100 blocks in size which have exactly one link. Display in ls -l format: find /bin /usr/bin -size -100 -links 1 -ls Find all files in /bin and /usr/bin which are 200 or more blocks in size and which have more than one link. find /bin /usr/bin -size +199 -links +1 Does a file have certain permissions? -perm There is a primary that allows you to test the permissions that users have for the file and to return TRUE or FALSE based on that. This primary is the -perm primary. The -perm primary takes a file mode as an argument, either a symbolic mode or an octal one (see 'chmod' in the QNX 4 Utilities manual for a description of symbolic mode syntax). The -perm primary behaves differently depending on whether you put a - in front of the file mode or not: -perm mode TRUE when the permissions of the file exactly match those of the mode argument. The setuid and setgid bits corresponding to the 's' symbolic mode or 04000 and 02000 (octal) mode bits are ignored for the purposes of this comparison. -perm -mode TRUE when the permissions of the file contain at least the permissions of the mode argument. The result will be true if the file has additional permissions not specified in the mode argument. The setuid and setgid bits _are_ significant for this comparison. Some examples: Find all files under the current working directory which are read only for all users. Display in ls -l format. find . -perm a=r -ls Find all setuid files. Display in ls -l format. find / -perm -u=s -ls More elaborate permission matching is possible when using the other available operators (parentheses, -o (OR), and ! (NOT)). MORE OPERATORS: -o (OR), ! (NOT) AND GROUPING BY PARENTHESES ------------------------------------------------------------ In addition to the -a (AND) operator, find supports a NOT operator (!), an OR operator (-o) and parentheses to override the standard operator precedence. NOT (!) The simplest of these is the ! (NOT) operator. If you put this in front of an expression, it means 'NOT (expression)' If the expression evaluates to TRUE, ! reverses it to FALSE, and vice- versa. The ! operator has a higher precedence than the -a (AND) operator. This means that the expression ! -name \*.c -name test.\* is the same as \( ! -name \*.c \) -name test.\* Note: The ! (NOT operator) must have a space on either side. For example, say you want to list all files in /bin that do not have execute permission for 'other', and which do have execute permission for the owner. You would do something like this: find /bin ! -perm -o=x -perm -u=x -ls You could reverse the order like this find /bin -perm -u=x ! -perm -o=x -ls and it would not matter, because of the precedence of the ! operator. OR (-o) The -o (OR) operator, like the -a (AND) operator, is a binary operator that joins two expressions like this: (expression1) -o (expression2) The new expression formed by this (something OR something) evaluates to TRUE if either expression1 and expression2 evaluates to TRUE, and evaluates to FALSE if both expression1 and expression2 evaluate to FALSE. As with the -a operator, there is an 'early-out' condition which will prevent the evaluation of the right hand side. If expression1 evaluates to TRUE, find knows that the expression as a whole is TRUE no matter what expression2 might evaluate to. For this reason, expression2 is not evaluated at all if expression1 is TRUE. How important this is cannot be stressed enough. Powerful find scripts often use action primaries on the right hand side of binary operators, relying on the conditional evaluation of the right hand side. (An example of such a find invocation can be found a little bit further in this document under the heading 'A Bigger Example'.) One way of thinking of it might be like this: (expression1) -o (expression2) can be thought of as 'if (expression1) return true, else return (expression2)' Likewise, (expression1) -a (expression2) can be thought of as 'if (expression1) return (expression2); else return false' The -o (OR) operator is of LOWER precedence than the -a (AND) operator. That is to say, -name \*.m -o -name spam\* -name \*.\* is identical to -name \*.m -o \( -name spam\* -name \*.\* \) i.e. the expression is TRUE if either the file ends with .m OR the file both starts with spam and is in the form something.something. PARENTHESES As you can see I have already started using the parentheses operators in the explainations of operator precedence. The parentheses operators may be used to group expressions that would otherwise be acted on differently due to operator precedence. Let's look at a couple examples. In the example for the ! (NOT) operator, we saw that the expression ! -name \*.c -name test.\* was the same as \( ! -name \*.c \) -name test.\* (Note that the parentheses are escaped to keep them from being acted on by the shell. Note also that parentheses, like the ! (NOT) primary must stand alone on the command line separated by white space. e.g. ... \( -name \*.\* ... is fine, but ... \(-name \*.\* ... will not work. ) Let's say you wanted to know when it was NOT true that both the name matched *.c and the name matched test.*. You would use parentheses as follows to force the NOT operation to be applied AFTER the AND operation between the two -name primaries: ! \( -name \*.c -name test.\* \) In another familiar example, a combination of AND and OR operators: -name spam\* -name \*.\* -o -name \*.m is identical to \( -name spam\* -name \*.\* \) -o -name \*.m . To cause the OR operator to be applied to the -name \*.\* and -name \*.m, we would do this: -name spam\* \( -name \*.\* -o -name \*.m \) meaning 'TRUE if the name starts with spam _and_ either the name is of the form *.* or the name ends with .m'. These examples can be expressed more concisely as simple patterns, of course, but they serve to illustrate the operators. A Bigger Example: The following example uses some primaries that I have not talked about yet. You might wish to look up in the Utility manual or see the find usage message ('use find' from the command line) for explainations of any primaries you don't recognise. Look to the end of the document for a complete description of everything in this next example. -error and -prune primaries will be discussed in the section following. Let's say you want to copy the entire contents of one directory onto another directory on another disk drive and are hell-bent on using the find utility to do it. You have noticed that the hardware that you're working with is a bit flakey so you want to check that the data is the same in both copies. So you run a find command like this: cd /srcdir if find .* * \ -type f \ \( \ -exec cp {} /destdir/{} "2>>errfile" \; \ -exec cmp {} /destdir/{} \; \ -exec cmp {} /destdir/{} \; \ -spawn echo {} is ok \; \ -o \ -exec cp {} /destdir/{} "2>>errfile" \; \ -exec cmp {} /destdir/{} \; \ -exec cmp {} /destdir/{} \; \ -spawn echo {} is ok after re-copying \; \ -o \ -error \ -o \ -exec echo {} did not copy ">errfile" \; \ -exec rm -f /destdir/{} "2>>errfile" \; \ \) \ -o \ -type d \ \( \ ! -exec mkdir /destdir/{} "2>>errfile" \; \ -prune \ ! -error \ -o \ -NOP \ \) then echo ALL FILES COPIED SUCCESSFULLY else echo Some files did not copy -- see the file 'errfile' fi MORE PRIMARIES: -PRUNE, -ABORT, -ERROR, -XDEV, -DEPTH ---------------------------------------------------------------------------- The -prune primary is used to keep find from recursing through the file currently being evaluated if it is a directory. If you recall from the section about the basics, I mentioned that the expression was applied to files and then after that, if find saw it was a directory, find would then go through all the files in that directory and so on, recursively. If -prune was evaluated as part of that expression, it would prevent that last step (going through the files beneath the directory being evaluated). The pruning effect is only for the file find is currently looking at, in the case where that file is a directory. The -prune primary will do nothing if find is operating in -depth mode. (I will explain why a bit later on.) In the big example we just finished looking at, you saw a find that was like find -type f \( \) -o \ -type d ! -exec mkdir /destdir/{} \; -prune -error For each file, it was doing if the file was a regular file, and if the file was a directory, it was trying to create a directory of the same name under another directory /destdir. If that mkdir _failed_ (it said ! (NOT) -exec, so it would be TRUE if the -exec failed) then the -prune primary was evaluated. What this would do is to prevent find from processing any of the files that lay beaneath that directory, because it had failed to create a destination for them. Another typical application of -prune is when you really don't want to recurse through subdirectories at all. You might do something like this: find . -level 1 -prune -type f -name \*.[ch] -exec textto -l {} \; The -level primary is necessary in that example to keep from pruning . itself - not what we wanted! The -abort primary, when evaluated, causes find to exit immediately with a non-zero exit status. This is useful in situations where it is critical that you stop processing files. (e.g. a crucial -exec has failed and you cannot proceed without it having succeeded) A simple example of -abort: If you want to find a file but don't want to scan further once you have found the file, you could do this: find / -name longlostsrc.c -print -abort Its return code would indicate failure, always, but you would get the location of longlostsrc.c printed to standard output. The -error primary, when evaluated, sets a flag which will cause find to exit with a non-zero exit status after it is finished processing all the files. This is used to 'manually' flag that an error has occurred. In the big example above (copying the files), the -error primary was used whenever a crucial -exec statement failed. 'MODE' PRIMARIES ---------------- The -xdev and -depth primaries are special primaries that set modes of operation for find. The modes are set when find parses its command line - it does not matter if the primary actually gets evaluated or not during the processing of the expression. For the purposes of evaluating the expression, both of these primaries act basically as no-ops which always have a value of TRUE. Xdev mode prevents find from recursing into directories that lie on different devices from the one it started out on. It prunes the directory tree at that point, and also prevents that directory itself from being evaluated by the expression. The -xdev mode works by remembering the device id of the directory on the command line. As it walks through the underlying directory tree, it simply skips over any directories that have a different device id. Note that it will not skip over regular (non-directory) files that have a different device id. If, for example, you have a floppy drive mounted but empty and a Qnx2 partition on your hard drive accessible under /qnx2 because you are running Qnx2fsys, find / -xdev -name \*.h will report an I/O error on the floppy (because it will try to stat the floppy to find out what it is - this requires that a formatted and dinited floppy disk be in the drive) and will not search the Qnx2 partition. If a floppy (with a filesystem on it) were in the drive, the floppy would be skipped because it, like the directory /qnx2, resides on a different device than /. The -depth mode causes a directory to be recursed into BEFORE, instead of after, it is evaluated by the expression. Because the files inside the directory will be processed before the directory itself is evaluated, the -prune primary is useless in -depth mode. An example that requires -depth: Let's say you wanted to use find to remove a directory tree with all the files in it. You have to empty one directory level before you can remove the directory itself. Therefore, you have to process the directory _after_ you process everything beneath it. Inside a shell script it might look like this: ($1 is the name of the directory you want to remove) find $1 -depth -type f -spawn rm -f {} \; -o \ -type d ! -spawn rmdir {} \; -error WHEN YOU ONLY ARE INTERESTED IN AN EXIT STATUS: the -NOP primary ---------------------------------------------------------------- Sometimes it is not desireable to print anything or execute anything in a script - perhaps you are only interested in finding out if some files exist, etc. This can be accomplished by using rather bizarre combinations of things, for example: find / -name \*.myextension -o -type f -type d -print Since no file can be type f _and_ type d, the -print will never be executed. The exit status will be 0 if there are files ending in .myextension, and non-zero if there are none. However, to make things simple there is a -NOP (all caps) primary in the QNX 4 find utility that can be used instead. Using -NOP prevents the implicit -print that would otherwise occur. So, instead of the contorted previous example, you could use this: find / -name \*.myextension -NOP The -NOP primary always evaluates to TRUE. EXTENDED FILENAME REPLACEMENT SYNTAX FOR -EXEC AND -OK ------------------------------------------------------ The -exec and -ok primaries support an extension to the {} filename replacement syntax that allows you to insert just _part_ of the filename. The full syntax is {[e[,s]]} (items in []s optional). e is the number of characters to truncate from the end of the filename, and s is the number of characters to truncate from the start of the filename. Let's look at some examples: To change all .obj files to .o files: find . -name \*.obj -exec mv {} {3}o \; To change all test.* files to myprog.* files: find . -level 1 -prune -name test.\* -exec mv {} ./myprog{,6} Note that in the latter example, we _had_ to prune to restrict the directory the files were in, because the stripping of characters is applied to the whole filename not just to the basename portion of it. In that example, we might get a filename of ./test.c so to obtain the '.c' we would need to strip 6 (to include the ./) as opposed to 4 characters. The ability to strip from the beginning of the filename is of limited usefulness, but occasionally comes in handy. The most frequent form of the extended syntax is the stripping of characters from the ends of filenames. BIG EXAMPLE EXPLAINATION ------------------------ This is the larger example found earlier in the text. Comment lines start with a # character. Note that the example script could not run with the # lines present since they would disrupt the find line (which must be all one line to work, hence the backslash characters at the end of each line in the find statement). cd /srcdir # cd /srcdir, then doing find .* *, will process the same files # (with the exception of srcdir itself) as would find /srcdir. # However, the filenames used in action primaries in the former # example would be relative to /srcdir, whle in the latter they # would all be preceded by /srcdir/. For this example I chose # the former to avoid having to use the extended filename # replacement syntax everywhere to strip the /srcdir/ that would # precede every filename. if find .* * \ # Evaluate every file and directory in the current working directory. # Note that .* is required because * will not match any filenames # starting with . -type f \ # If the file is a regular file (not a directory, character or block # special, fifo, or symbolic link) \( \ # Parentheses. the -o following the matching end parentheses applies # to this following subexpression as a whole. The presence of -o # operators within this subexpression forces this, since otherwise # the -o before the -type d primary would apply only to the # subexpression of the previous -o. -exec cp {} /destdir/{} "2>>errfile" \; \ # copy the file being evaluated to /destdir, write stderr to the file 'errfile' # if the copy fails, find will skip to the next -o -exec cmp {} /destdir/{} \; \ # compare the source to the destination. If the comparison fails, skip to # the next -o -exec cmp {} /destdir/{} \; \ # compare the source to the destination again. If the comparison fails, skip to # the next -o -spawn echo {} is ok \; \ # Display to stdout that the copy we just made was ok. # If we got this far, the expression is true and find will proceed to # the next file. -o \ # If we are here, an error occurred copying the file, or the original # did not match the copy. So we try the same thing again: -exec cp {} /destdir/{} "2>>errfile" \; \ -exec cmp {} /destdir/{} \; \ -exec cmp {} /destdir/{} \; \ -spawn echo {} is ok after re-copying \; \ # If we got this far, the copy succeeded the second time round. The # expression is true and find will proceed to the next file. -o \ # If we are here, an error occurred during the second attempt to # copy and verify the file. -error \ # -error always evaluates to FALSE after setting the error flag, # so this just falls through into the subexpression following the next -o -o \ # we can only get here through the evaluation of the -error which is # always FALSE. -exec echo {} did not copy ">>errfile" \; \ # write into the error file that the file being evaluated was not copied # successfully -exec rm -f /destdir/{} "2>>errfile" \; \ # remove the faulty destination file \) \ # end of -type f subexpression. -o \ # we will end up here if the file is not a regular file, or if the # attempt to copy the file was unsuccessful -type d \ # test to see if the file a directory. If it isn't, the following # subexpression won't be evaluated, and find will proceed to the next # file \( \ # the parentheses here are necessary because without them, a failure # of the -type d test would result in the -NOP being executed, thus # potentially affecting the exit status ! -exec mkdir /destdir/{} "2>>errfile" \; \ # create a directory under the destination of the same name as this # directory. The ! (NOT) operator will cause this to evaluate to TRUE # if the mkdir() fails. If the mkdir() succeeds, this will evaluate # to FALSE and find will skip ahead to the -NOP statement. -prune \ # the mkdir has failed, so prune the source at this point. The directory # at the destination doesn't exist - there is no place to copy any original # files under this directory ! -error \ # set the error flag. The ! (not) is in front of it so it evaluates to TRUE, # causing the -o -NOP to be skipped -o \ # we arrive here if the mkdir succeeded. -NOP \ # The -NOP is here to make the expression evaluate TRUE. \) # we fall past the end if the file was neither a regular file nor # a directory, or if the copy/verify failed. So, for these cases # the expression evaluates FALSE # now, depending on the exit status of the find utility, we either # write that everything copied ok, or we say that errors occurred. then echo ALL FILES COPIED SUCCESSFULLY else echo Some files did not copy -- see the file 'errfile' fi This example was a contrived example intended to demonstrate as many basic features of find as possible. Some parts of it were written a certain way (e.g. the creation of destination directories) with an inverted logic to demonstrate the ! operator. I actually did need to run a find script similar to this one once, in a rather bizzarre situation where the hardware was failing in a certain way.