MacOS find with “-f path” doesn’t work


man find:

-f path: Add path to the list of paths that will be recursed into. This is useful when path begins with a character that would otherwise be interpreted as an expression, namely “!” , “(” and “-”.

But why

find ./test-folder -type f -name '*.txt'

works, whereas

find -f ./test-folder -type f -name '*.txt'

doesn't work?

What is the error then?

./test-folder: illegal option -- t
usage: find [-H | -L | -P] [-EXdsx] [-f path] path ... [expression]
       find [-H | -L | -P] [-EXdsx] -f path [path ...] [expression]

Best Answer

Let's change the command a bit and see what happens:

% find -f x -type f
x: illegal option -- t
% find -f x -t
x: illegal option -- t
% find -f x -H
[recurses into x] 

I'm not at all sure why it would print the given path in the error message, but apart from that, it's clear that it's the following -t that produces the error. That's because the -type argument is taken as the shorthand for options -t, -y, -p, and -e, the same way something like ls -l -aR has the three options -l, -a, and -R, and not just the option -l and filename -aR. Now, find doesn't have a -t option, so it complains. -H works, as above, and of course find -f . -depth would complain about the option letter e.

You need to end the list of options with --, to get the subsequent args taken as non-options. This is the same with find as with other tools e.g.:

% find -f ./test-folder -- -type f -name '*.txt'

or if you want to list a file called -aR:

% ls -l -- -aR

Of course, with other tools, the issue is mostly with things like ls *.txt where a filename starting with - could be an issue. One seldom uses globs with find, but the idea is the same.

Same as with other tools, one doesn't usually need -- with find, as long as the path given as the first non-option is "nice", e.g. .. An argument not starting with a dash implicitly ends option processing. But you could use --:

% find -- . -type f

Of course you could also use a glob with find, e.g. something like find -- *.d -name "*.conf". Not that the -- helps, since a name starting with a dash (-foo.d) might now be taken as an invalid predicate.

And of course if the expression starts with something that doesn't look like an option (( or !), that also stops option processing, like in the command mentioned in the comments:

find -f ./test-folder \( -type f -name '*.txt' \)

That also doesn't help if a filename starts with a dash, so that's why they have the -f option. Or you could always just prefix the filename with ./, affecting the output similarly. As an added bonus, this would also work with a glob, e.g. ./*.d.

Note that GNU find is different here, it tries to interpret unknown options as predicates:

$ find -H -t
find: unknown predicate `-t'

but of course that's because it doesn't require explicitly giving a path to search, but instead defaults to . if none is given. E.g. these are the same:

$ find -H -type f
$ find -H . -type f

It also doesn't deal with stacked options, though that's likely fine since one often doesn't use more than one with find (the -H, -L and -P options all control the treatment of symlinks, and override each other, while -O and -D are for optimization and debugging):

$ find -HO1 -type d
find: unknown predicate `-HO1'

It doesn't seem possible to give GNU find a something like -type as a filename on the command line. You'll have to use ./-type instead, or [pass the filenames through -files0-from].

Related Question