视频课程目录16
第 06 · HHY 1.7.0
Read and Write Files
Read a directory of text files and save an uppercase copy.
学习进度保存在此浏览器,无需登录。
章节与讲稿
点击时间跳转视频,展开标题查看讲稿与代码。
A useful file task
In this lesson, we will turn a folder of text files into a small inventory, then save an uppercase copy of one file. The original files stay unchanged. Open the downloaded examples folder before running the command. All reads use the fixtures directory, and all generated files go into out. This separation makes it easy to inspect the result and repeat the demonstration without touching your own documents.
hhy run files.hhyRun the complete example from its examples directory.
Paths describe locations
A Path represents a filesystem location. A String represents text, so wrap a filename in path when calling these file APIs. The name and extension fields are read only, and the extension includes its leading dot. A relative data path starts at your current working directory. That is why our run instructions first move into examples. Later, module imports will have a different relative path rule.
let source = path("fixtures/notes.txt")
print(source.name)
print(source.extension)notes.txt .txt
Run the complete example from its examples directory.
Discover matching entries
The files function produces a lazy stream of filesystem entries. Our pattern selects text files, including matches below nested directories. We explicitly retain regular files, then project each entry to its name. Sorting makes our teaching output deterministic instead of depending on directory traversal order. Collect consumes the stream and creates a List. Notice that a filesystem entry contains metadata; it is not simply a filename string.
let names = path("fixtures")
|> files("**/*.txt")
|> where { file -> file.is_file }
|> map { file -> file.name }
|> sort_by({ order: "asc" }) { name -> name }
|> collect
print(names)Run the complete example from its examples directory.
Read only what you need
Use read_text when you need one complete text value. Use read_lines when your processing works one line at a time. Both are text APIs and expect valid UTF eight input. Images and archives belong with the byte APIs instead. Here, read_lines supplies a stream directly, so we do not add stream after it. Each callback returns a transformed line without changing the original file on disk.
read_lines(source)
|> map { line -> upper(line) }Run the complete example from its examples directory.
Save the transformed stream
Now add a terminal operation. Save_lines consumes the line stream and adds a line ending for each value. Create_parents allows the out directory to be created on the first run. The documented save operation writes a temporary sibling file and commits the replacement atomically. That prevents readers from seeing a half written target. It does not mean several different output files become one transaction together.
read_lines(source)
|> map { line -> upper(line) }
|> save_lines(path("out/upper.txt"),
{ create_parents: true })Run the complete example from its examples directory.
Inspect the actual output
Read the saved file back and print it. This checks the complete path from discovery and transformation to disk output. The uppercase text should contain the same two lines as notes, with their letters changed. Trim is only used for clean terminal presentation here; it is not part of the saved transformation. Open both files side by side and verify that the input still contains its original lowercase text.
read_text(path("out/upper.txt"))
|> trim
|> printFIRST LINE SECOND LINE
Run the complete example from its examples directory.
Write versus append
Write_text replaces the target by default, while append_text adds content to an existing file. We intentionally reset our demonstration journal at the start of every run, then append the second line. That makes repeated runs predictable. Pay attention to the explicit newline characters: a text write receives exactly the text you supply. Choose save_lines when your input already consists of separate line values and needs line endings.
write_text(path("out/journal.txt"), "first\n")
append_text(path("out/journal.txt"), "second\n")Run the complete example from its examples directory.
Protect an existing target
A common mistake is assuming a write will refuse to replace an existing file automatically. Set overwrite to false when replacement is not acceptable. This deliberate attempt fails because our journal already exists. The catch block lets the demonstration continue, and the journal still contains first and second. We will study structured errors in lesson nine. For now, compare the protected failure with the successful explicit reset above.
try {
write_text(path("out/journal.txt"), "replace",
{ overwrite: false })
} catch err {
print("overwrite prevented")
}overwrite prevented
Run the complete example from its examples directory.
Practice: inventory another fixture
For practice, add a third text file inside fixtures and rerun the script. The sorted inventory should grow, while the uppercase copy should still come from notes. Then change the selected source to your new fixture and predict its output before running again. Keep writes inside out. You have now connected Path values, lazy directory traversal, line processing, and controlled saving into one useful automation script.
hhy run files.hhy
# Inspect out/upper.txt and out/journal.txtRun the complete example from its examples directory.
