All chapters21
On this page

Guide · Chapter 4

Files and Paths

Walk directories, read text, and write results safely.

1.7.1 controlled concurrency and atomicity

This release fixes resource lifetimes and delivers seven experimental APIs, disabled by default. Review state ownership, cooperative file locks, strict transactions and bounded-task guarantees before enabling them.

Read the complete guide and examplesIncludes the guarantee matrix, failure handling and actual validation evidence

Path is not String

Every filesystem API requires Path. path(text) normalizes lexically: it collapses repeated separators and ., and resolves removable .. segments without accessing the filesystem or resolving symlinks. A relative Path is always based on the process startup directory, not the importing file.

example.hhy
let source = path("./src/../src/main.c")
let target = path_join(source.parent, "runtime.c")

print(source)
print(source.name)
print(source.extension)
print(source.parent)
print(target)

name, extension, and parent are read-only Path fields—not path_name(), path_extension(), or path_parent() functions. extension includes the leading dot and is empty when absent; path_join(base, child) returns a new combined Path.

files: traversal, globs, and metadata

example.hhy
path("./logs")
    |> files("**/*.log")
    |> where { file -> file.size > 1mib }
    |> flat_map { file -> read_lines(file.path) }
    |> where { line -> contains(line, "ERROR") }
    |> save_lines(path("errors.txt"))

files(root, pattern, options?) returns a lazy Stream<File | Directory> and excludes the traversal root itself. Patterns support *, ?, and **. Directory symlinks are not followed by default; { follow_symlinks: true } opts in with cycle detection.

FieldMeaning
pathFull Path
nameFile or directory name
extensionExtension including its leading dot
sizeSize as Bytes
createdCreation time, or null when unavailable
modifiedModification time
is_file / is_dir / is_symlinkObject-kind flags

File and Directory are system objects, not Maps. Map required fields into an ordinary Map before JSON encoding.

Reading text and binary data

funcread_text#

read_text(Path) -> String

Read an entire UTF-8 file as String.

funcread_lines#

read_lines(Path) -> Stream<String>

Lazily read UTF-8 lines with terminators removed.

funcread_bytes#

read_bytes(Path) -> BytesBuffer

Read an entire binary file as BytesBuffer.

Writing, appending, and atomic saves

example.hhy
let input = path("notes.txt")
let backup = path("backup-notes.txt")

write_text(input, "first line\n", { overwrite: true })
append_text(input, "second line\n")
copy(input, backup, { overwrite: false })

read_lines(backup)
    |> map { line -> upper(line) }
    |> save_lines(path("backup/upper.txt"), { create_parents: true })

funcwrite_text#

write_text(Path, String, Map?) -> Path

Atomically replace with String, supporting overwrite/create_parents.

funcappend_text#

append_text(Path, String) -> Path

Append String to the end of a file.

funcwrite_bytes#

write_bytes(Path, BytesBuffer, Map?) -> Path

Atomically replace a file with BytesBuffer.

funcsave_text#

save_text(String | Stream<String>, Path, Map?) -> Path

Atomically save a String or pull a text Stream directly to disk.

funcsave_lines#

save_lines(Stream<String>, Path, Map?) -> Path

Write a String Stream with LF per item and atomically replace the target.

write_text, write_bytes, save_text, and save_lines accept overwrite (default true) and create_parents (default false). They commit through a same-directory temporary file plus rename; overwrite: false uses atomic no-replace to prevent check-then-write races.

Copy, move, remove, and dry-run

funccopy#

copy(Path, Path, Map?) -> Path

Copy a file with atomic no-replace; the target parent directory must already exist.

funcmove#

move(Path, Path, Map?) -> Path

Move or rename a file while respecting overwrite options.

funcremove#

remove(Path) -> Path

Remove an explicit Path and return it.

sh
hhy run --dry-run backup.hhy
hhy run backup.hhy

The target parent directory for copy and move must already exist; the current 1.7.1 runtime does not process their create_parents option. The example uses overwrite: false; choose a new backup filename before repeating it. Review the dry-run plan before executing a script that copies, moves, or removes files.

Look up the complete API

Paths and files API Reference →Look up every signature, parameter form, and stable function anchor.