scriptling.find
The scriptling.find library finds files and directories under a path by name, type, modification time, and size — similar in spirit to the Unix find command. It fills the gap between glob (which matches names but cannot filter on metadata) and a hand-rolled os.walk loop.
When searching recursively, entries are stat’d and filtered concurrently using the same bounded worker-pool model as scriptling.grep, so large trees are scanned in parallel.
The library exposes two functions: path() returns matching paths as a list of strings, and entries() returns the same matches as a list of dicts carrying size, mtime, and is_dir per entry. Use entries() when you need the metadata (e.g. comparing two trees without re-reading bytes); use path() when only the strings are needed, since it skips the per-entry stat in the no-filter common case.
Available Functions
| Function | Description |
|---|---|
path(path, **kwargs) |
Find files/directories matching the given filters; returns list[str]. |
entries(path, **kwargs) |
Same filters as path(), but returns list[dict] with path, size, mtime, and is_dir per match; opt-in hash, link_target, metadata fields. |
Functions
path(path, *, recursive=True, type="any", name="", mtime_min=None, mtime_max=None, size_min=None, size_max=None, include_hidden=False, follow_links=False, max_depth=None)
Finds files and directories under path matching the supplied filters and returns their paths as a list of strings. Results are in arbitrary order; an empty list is returned when nothing matches.
If path is a single file, it is checked directly against the filters and returned (as a single-element list) when it matches.
Parameters:
path(str): Directory (or file) to search under.recursive(bool, keyword-only): Descend into subdirectories. Default:True. WhenFalse, only the immediate children ofpathare examined.type(str, keyword-only): Restrict to"file"(regular files),"dir"(directories), or"any". Default:"any".name(str, keyword-only): Shell-style glob pattern matched against each entry’s base name, e.g."*.md". Empty matches everything. Default:"".mtime_min(floatorNone, keyword-only): Include only entries modified at or after this epoch time (seconds).None= no lower bound. Default:None.mtime_max(floatorNone, keyword-only): Include only entries modified at or before this epoch time (seconds).None= no upper bound. Default:None.size_min(intorNone, keyword-only): Include only entries whose size in bytes is>=this value.None= no lower bound. Default:None.size_max(intorNone, keyword-only): Include only entries whose size in bytes is<=this value.None= no upper bound. Default:None.include_hidden(bool, keyword-only): WhenTrue, entries whose name starts with.are matched; whenFalse(the default) they are skipped along with their subtrees.follow_links(bool, keyword-only): Follow symlinks if they resolve within allowed paths. Default:False.max_depth(intorNone, keyword-only): Maximum recursion depth (1= immediate children only).None= unlimited. Default:None.
Returns: list[str]: matching paths.
Raises: Error: if path is outside the allowed paths (see Security Considerations).
import scriptling.find as find
import time
# All markdown files under /docs (recursive by default)
docs = find.path("/docs", name="*.md", type="file")
# Markdown files modified in the last 24 hours
recent = find.path("/docs", name="*.md", type="file",
mtime_min=time.time() - 86400)
# Large log files (> 100 MiB)
big_logs = find.path("/var/log", name="*.log", type="file",
size_min=100 * 1024 * 1024)
# All directories named "node_modules" (descend into dot-dirs too)
dirs = find.path("/project", type="dir", name="node_modules",
include_hidden=True)
# Only immediate children, no descent
top = find.path("/data", recursive=False)entries(path, *, recursive=True, type="any", name="", mtime_min=None, mtime_max=None, size_min=None, size_max=None, include_hidden=False, follow_links=False, max_depth=None, include_metadata=False, include_hash=False, include_symlinks=False)
Like path() — same filters, same semantics — but returns a list[dict] with one dict per match, so the caller can compare trees without re-reading bytes. Every matching entry is stat’d; use path() instead when only the strings are needed.
Additional parameters (beyond those shared with path()):
| Parameter | Type | Description |
|---|---|---|
include_metadata |
bool |
When True, file_perm is populated (extracted from the entry stat, no extra syscall). |
include_hash |
bool |
When True, each file is crc64-hashed and the hex checksum is returned in hash. Use for definitive change detection — two entries with matching hash have identical bytes. |
include_symlinks |
bool |
When True, symbolic link entries are yielded with their link_target set to the symlink target string, instead of following the link. |
Each entry dict has the keys:
| Key | Type | Description |
|---|---|---|
path |
str |
The matching entry’s path. |
size |
int |
Size in bytes. 0 for directories. |
mtime |
float |
Modification time as epoch seconds (matches the unit used by mtime_min/mtime_max). |
is_dir |
bool |
True when the entry is a directory. |
file_perm |
int or None |
File permission bits. None unless include_metadata=True. |
hash |
str or None |
Hex-encoded crc64 checksum of file content. None unless include_hash=True. |
link_target |
str or None |
Symlink target path. None unless include_symlinks=True. |
Parameters: identical to path(), plus the three above. The root itself is never included in the result, and entries are returned in arbitrary order.
Returns: list[dict]: one dict per match.
Raises: Error: if path is outside the allowed paths.
import scriptling.find as find
import time
# Sync-relevant metadata: every markdown file with its size and mtime
for e in find.entries("/docs", name="*.md", type="file"):
print(e["path"], e["size"], e["mtime"])
# Build a {path: mtime} index for differential sync
mtimes = {e["path"]: e["mtime"] for e in find.entries("/site", type="file")}
# Directories only — is_dir is always True here, but size and mtime are
# still populated for the directory itself.
dirs = find.entries("/project", type="dir", name="node_modules")
# Hash-based change detection — only upload when bytes differ
for e in find.entries("/site", include_hash=True, type="file"):
print(e["path"], e["hash"])
# Detect symbolic links without following them
for e in find.entries("/project", include_symlinks=True):
if e["link_target"]:
print(e["path"], "->", e["link_target"])Security Considerations
This is an extended library, requiring registration in Go, see Library Registration.
find provides read access to the host filesystem (directory listings and stat calls). When embedding in Go, access is restricted to the allowedPaths passed to RegisterFindLibrary(p, allowedPaths): path traversal (../) is blocked automatically and symlinks resolving outside the allowed paths are skipped. See Library Registration and the Security Guide.
See Also
- glob: Shell-style wildcard matching by name
- pathlib: Object-oriented filesystem paths
- scriptling.grep: File content search, using the same parallel worker model