Why does find sometimes match its command line path argument?
find
traverses the specified directory tree(s), and evaluates the given expression for each file that it finds. The traversal starts at the given path. Here's a summary of how find . -name foo
operates:
- First path on the command line:
.
- Does the base name (
.
) match the patternfoo
? No, so do nothing.
It so happens that/tmp/foo
is another name for the same directory. Butfind
doesn't know that (and it isn't supposed to try to find out). - Is the path a directory? Yes, so traverse it. Enumerate the entries in
.
, and for each entry, perform the traversal process.- The directory is empty: it contains no entry other than
.
and..
, whichfind
does not traverse recursively. So the job is finished.
- The directory is empty: it contains no entry other than
- Does the base name (
And find /tmp/foo
:
- First path on the command line:
/tmp/foo
- Does the base name (
foo
) match the patternfoo
? Yes, so the condition matches.- There is no action associated with this condition, so perform the default action, which is to print the path.
- Is the path a directory? Yes, so traverse it. Enumerate the entries in
/tmp/foo
, and for each entry, perform the traversal process.- The directory is empty: it contains no entry other than
.
and..
, whichfind
does not traverse recursively. So the job is finished.
- The directory is empty: it contains no entry other than
- Does the base name (
It so happens that .
and /tmp/foo
are the same directory, but that's not enough to guarantee that find
has the same behavior on both. The find
command has ways to distinguish between paths to the same file; the -name
predicate is one of them. find /tmp/foo -name foo
matches the starting directory as well as any file underneath it that's called foo
. find . -name .
matches the starting directory only (.
can never be found during a recursive traversal).
There is no normalization of the command line arguments before the tests are applied. Thus the results differ depending on the used path (if symlinks are involved):
cd /tmp
mkdir foo
ln -s foo bar
find /tmp/foo -name foo
find /tmp/bar -name foo
In "your case" both calls would give the same result which might be (more) confusing. You can use -mindepth 1
if you want the starting points ignored (may be non-POSIX).
(gnu) find shows any matches found within the path provided to the command because it starts its comparison with the commandline arguments, descending deeper into the directory structure from there (thus, -maxdepth 0
confines the tests to the base level or the commandline arguments only, whereas -mindepth 1
skips the commandline arguments as man find
explains). This is the reason why find /tmp/foo -name 'foo'
will yield one match even if the directory itself is empty.
find . -name 'foo'
on the other hand will not yield any result because .
(dot) is a special file that acts like a hardlink to the same inode as /tmp/foo
- it is like a seperate (though special) filename and not a symbolic link or an expression that is subjected to pathname expansion by the shell. Therefore, the first test applied by find to the commandline arguments in the given example will not show any matches, since .
indeed does not match the name pattern defined in -name 'foo'
. Neither does /tmp/foo/.
since a test for a -name
pattern is performed on the basename of the path only (see man find
), which here again is .
.
While this behavior may not be expected or appear intuitive from a user perspective (and yes, it had me confused at first as well), it does not constitute a bug but corresponds with the logic and functionality described in the man and info pages for (gnu) find.