Make find Follow Symlinks: Choose -P, -H, or -L
Practice the foundations with the 100-card Linux command-line deck in English. It covers commands including
findandln -s; this article supplies the specific symlink exercises.
find -L ./project -type f -print can return three paths even when they reach only two files. One of those files can live outside project. Following symbolic links changes both where find walks and what its type tests examine.
To make find follow symlinks throughout a search, put -L before the starting path. Use -H to follow only links supplied as starting points, or -P to inspect links themselves. The small tree below makes those choices visible with searches that only print paths.

Choose which links to follow
A symbolic link stores a path to another filesystem entry. Following it, also called dereferencing it, lets find examine the target. If that target is a directory, following the link also lets the search enter that directory.
| Mode | A symlink supplied as the starting path | Symlinks encountered inside the search |
|---|---|---|
-P |
Inspect the link itself | Inspect the links themselves |
-H |
Follow the target when possible | Inspect the links themselves |
-L |
Follow the target when possible | Follow the targets when possible |
-P is the default on GNU find and the native macOS find used here. The GNU traversal options document these modes. POSIX find specifies -H, -L, and the default behavior when neither is supplied.
Keep two questions separate: which entries will the search reach, and whose type will it test? A link to a regular file doesn't add a directory to traverse, but following it still changes whether -type f matches.
In the examples, -type f selects regular files, -type l selects symbolic links, and -print prints each matching path. The mode determines whether the type test examines the link or its target. A type test filters the results; it doesn't tell find which links to follow.
Build a tree with an outside target
Run this from a location where you can create a new find-links-lab directory. Python 3 is required for setup. It fails if that directory already exists; choose a fresh location rather than reusing a project.
python3 - <<'PY'
from pathlib import Path
root = Path("find-links-lab")
root.mkdir()
(root / "project").mkdir()
(root / "shared").mkdir()
(root / "project/local.txt").write_text("local demonstration\n")
(root / "shared/reference.txt").write_text("shared demonstration\n")
(root / "project/file-link").symlink_to("../shared/reference.txt")
(root / "project/dir-link").symlink_to("../shared", target_is_directory=True)
(root / "project/missing-link").symlink_to("../missing.txt")
(root / "entry").symlink_to("project", target_is_directory=True)
PY
After setup succeeds, run:
cd find-links-lab
The complete tree is:
find-links-lab/
├── project/
│ ├── local.txt
│ ├── file-link -> ../shared/reference.txt
│ ├── dir-link -> ../shared
│ └── missing-link -> ../missing.txt
├── shared/
│ └── reference.txt
└── entry -> project
There is no missing.txt. Its link is dangling: the stored path doesn't resolve to an existing target. Relative link targets are resolved from the directory containing the link. That's why ../shared in project/dir-link reaches the sibling directory.
The nine searches below were executed with native /usr/bin/find on macOS 26.6.1 arm64 on October 22, 2026, local time; setup used CPython 3.14.7. Each search exited with status 0 and no diagnostic output. GNU behavior was checked against documentation; this fixture wasn't executed on GNU/Linux.
Keep the starting paths exactly as shown, without a trailing slash. Displayed result sets are sorted for readability; the order in your terminal may differ.
Start at the real directory
Both of these commands print only the local regular file:
find -P ./project -type f -print
find -H ./project -type f -print
Complete output for each command:
./project/local.txt
./project is an actual directory, so -H has no starting-point link to follow. All three links are encountered inside it and remain links for the type test. None matches -type f.
Now follow the inner links too:
find -L ./project -type f -print
Complete output:
./project/dir-link/reference.txt
./project/file-link
./project/local.txt
file-link matches because its target is a regular file. dir-link leads into shared, where reference.txt matches. The missing target supplies no regular file to match.
Notice the spelling: output still says ./project/dir-link/reference.txt, not ./shared/reference.txt. -print reports the path encountered during the walk. It doesn't rewrite the result as a resolved target path.
Those three lines reach two distinct regular files in this fixture: project/local.txt and shared/reference.txt. Both file-link and dir-link/reference.txt reach the shared file. Here, counting output lines counts matching paths; it doesn't count unique targets.
This also shows why the starting path isn't a containment boundary under -L. The shared directory and its file live outside the physical project directory. An output prefix of ./project/ doesn't establish that the target is stored there.
Make the starting path a link
To see when -H makes a difference, start at ./entry, which points to project:
find -P ./entry -type f -print
This prints nothing. find examines the starting link itself, which fails -type f, and doesn't enter its directory target. An empty result is expected here; it isn't evidence of a failed command.
find -H ./entry -type f -print
Complete output:
./entry/local.txt
-H follows the explicitly supplied entry link and enters project. It doesn't follow the three inner links. The printed path retains entry because that's how the search reached the local file.
find -L ./entry -type f -print
Complete output:
./entry/dir-link/reference.txt
./entry/file-link
./entry/local.txt
Now both the starting link and the inner links are followed. Changing the entry path changes the printed prefix, while these results reach the same two regular files as the earlier -L ./project search.
Find symbolic links, including the dangling one
To list the three link entries inside the physical directory:
find -P ./project -type l -print
Complete output:
./project/dir-link
./project/file-link
./project/missing-link
With -L, the type test usually sees the target instead. Run:
find -L ./project -type l -print
Complete output:
./project/missing-link
The two working links are tested as a directory and a regular file. The dangling link has no target to inspect, so its own link type is used. This command finds the broken symlink in this fixture; the GNU type-test reference describes this change in what -type examines under -L.
Treat matches as paths to inspect, rather than proof that targets were deleted. A dangling link can point to a path that never existed, as missing-link does here. GNU's traversal reference also documents using the link's properties when permissions prevent following it. Permission failures weren't tested in this fixture. Its symbolic-link reference explains the general rule when a link can't be followed.
To enter through a linked root while listing its inner links, use:
find -H ./entry -type l -print
Complete output:
./entry/dir-link
./entry/file-link
./entry/missing-link
The starting entry link isn't included: -H tests its directory target. The inner links are included because -H leaves them unfollowed.
Six decisions to practice
Cover the answers and write the command or result before running anything. Keep using the unchanged fixture.
- Enter through
./entry, list regular files, and avoid following inner links. Which mode gives that behavior? - Why do
-P ./project -type fand-H ./project -type fproduce the same set? - Does
-L ./project -type fprint the literal path./shared/reference.txt? Give the two paths through which it reaches that file. - List every symlink inside
project, including the working ones. Which command should you use? - Under
-Lin this fixture, why doesmissing-linkpass-type lwhilefile-linkfails it? - A script counts three lines from
-L ./entry -type f -printand reports three distinct files physically insideproject. Which two assumptions fail?
Check the reasoning
find -H ./entry -type f -print. It follows the starting link only. The complete result is./entry/local.txt.- The starting point is already a real directory.
-Hchanges handling of a linked starting point; it leaves all three links encountered insideprojectalone, just as-Pdoes. - No. The paths are
./project/file-linkand./project/dir-link/reference.txt. The search retains each encountered path rather than replacing it with the target's spelling. find -P ./project -type l -print. It returnsdir-link,file-link, andmissing-link, each with the./project/prefix. Working targets don't change these link-type matches under-P.- The missing target can't be inspected. Its link type remains available. The working
file-linkis tested using its regular-file target, so it passes-type finstead. - Path count isn't unique-file count, and the prefix isn't physical containment. Two paths reach
shared/reference.txt, outsideproject; the remaining path reachesproject/local.txt.
Return to the choice you missed
Save a short recall prompt for any mistaken prediction: “Follow a linked starting directory but leave nested links alone,” or “Does -L make printed paths unique?” Put the answer and a one-sentence explanation on the back. For a command question, include the starting path in the prompt; choosing between ./project and ./entry is part of the exercise.
Try the prompt from memory on a later day, then rerun the small command to check it. Regular recall and spaced review can help retain these distinctions. Keep practical checks alongside review, especially when applying an option to a different tree.
Once link handling matches your intention, find -prune covers deliberately skipping directories, and find -mtime covers selecting files by age. Choose the traversal behavior first so those later tests operate on the entries you mean to examine.
AI assisted substantially with research, writing, editorial work, fixture execution, and exercise construction; the illustration is AI-generated. The nine displayed command variants were executed with demonstration files on native macOS. GNU documentation was consulted; no GNU/Linux runtime test or human editorial review is claimed.