The last sentence of the question suggests the problem is not with following symlinks in general (and e.g. applying -newer
to the symlink in one case, while to the target in another case), but with following symlinks to directories and thus descending where you don't want.
Per specification, find
shall detect infinite loops, so a livelock shouldn't happen. Resources (including time) being wasted may be a problem and I understand your concern.
First of all, use -L
to allow find
to follow symlinks you want it to follow. Then you need a way to detect other symlinks.
In find -L
-type l
is never true, but you can detect symlinks with -exec test -L {} \;
(remember -exec … \;
is also a test, it succeeds iff the inner command exits with status 0). This way you still can apply some test(s) specifically to symlinks and -prune
(or not) accordingly.
For example the following command will descend only symlinks whose names start with foo
or end with bar
:
find -L . -exec test -L {} \; ! \( -name 'foo*' -o -name '*bar' \) -prune -o -print
Whatever expression you originally wanted, place it after -prune -o
. In the above command the "original" expression is -print
. Remember -exec
suppresses the implicit -print
, so if you need -print
then add it explicitly (like we did).
Note that -name
tests the name of the symlink itself. If you want to test the name of the target then you need realpath
, possibly basename
and some shell code; example:
find -L . -exec test -L {} \; -exec sh -c ' case "$(basename "$(realpath "$1")")" in baz* ) exit 0 ;; *qux ) exit 1 ;; esac exit 0' find-sh {} \; -prune -o -print
The above command will -prune
any symlink to any file whose name starts with baz
, but it will descend symlinks to directories with names ending with qux
(unless starting with baz
) and finally -prune
all other symlinks.
Notes:
realpath
andbasename
are not portable.realpath
resolves all symlinks.- The behavior in case of a broken symlink may or may not be what you expect.
- Invoking a separate
test
for every file will hurt performance, but I have found no better way. Invokingsh
,realpath
andbasename
is also bad in this matter, but here it happens only for symlinks, so in the majority of cases the impact will be limited. find-sh
is explained here: What is the second sh insh -c 'some shell code' sh
?- In the examples pathnames that get
-prune
d will not be-print
ed. Change the final fragment to-prune -false -o -print
and they will be printed. See explanation; a portable alternative to-false
is there in case you need it.