第18章 File类与Dir类
掌握文件重命名/复制/删除、目录遍历/创建/删除、属性和路径操作,并用Find、Tempfile、FileUtils建立受控递归与原子替换。
学习目标
- 能解释变更、复制与删除文件与目录内容的读取在“第18章 File类与Dir类”中的责任边界
- 能围绕“怎样让路径、目录遍历、临时文件和复制删除操作保持原子与可回退?”运行正常与故障轨迹并定位首个分岔
- 能用“第18章 File类与Dir类的输入样本、接收者与方法、关键状态前后值、正常与失败输出、异常或退出状态,以及复位后的再次运行记录。”证明“路径归属、符号链接策略、权限与替换顺序在每次文件变更前明确。”
来源、版次与运行边界
“第18章 File类与Dir类”以作者维护的第 5 版支持页核定 2016 年 3 月 12 日首刷、两位作者、松本行弘监修以及四部分 23 章目录;逐章程序清单、练习答案和勘误只作为公开支持材料,不被冒充为原书全文。
对“第18章 File类与Dir类”而言,中文解释、示例、交互、练习和答案均为独立教学重写;站内中文章名是与官方 23 章顺序对应的课程映射,不宣称是日文小节的逐字翻译,也不从公开程序清单复制整段实现。
“第18章 File类与Dir类”固定在 Ruby 2.3 语境;Ruby 2.3.0 官方文档与稳定版发布说明用于核对当时可用的语言和标准库行为。现代 Ruby 的差异只能另列迁移说明,不能静默改变本页示例的版本结论。
围绕“怎样让路径、目录遍历、临时文件和复制删除操作保持原子与可回退?”,本页验收“第18章 File类与Dir类的输入样本、接收者与方法、关键状态前后值、正常与失败输出、异常或退出状态,以及复位后的再次运行记录。”。先预测正常轨迹,再只注入“先删除目标再写新文件,中途失败后同时失去旧版本和新版本”;若无法定位第一处状态分岔,就拒绝当前解释。
从“路径字符串指向一个稳定文件”开始纠正
Path是namespace中的name,不是稳定object capability;检查后到操作前,它可被替换、重命名或经symlink指向别处。先于File API。
先预测:File.file?(path)为true后再delete是否安全?不保证;另一个process可在两步间替换path。普通工具尽量直接执行目标operation并处理exception;高敏感场景使用directory handle/sandbox/platform secure API减少TOCTOU。
File类:变更、复制与删除文件
File继承IO能力并增加path/metadata operations。File.rename更改directory entry,同filesystem上常可作为atomic replacement基础,但cross-device会失败,overwrite与open-handle behavior因OS不同。必须按部署平台验证。
source = "draft.txt"
destination = "published.txt"
File.rename(source, destination)复制文件
FileUtils.cp复制contents并有metadata options;IO.copy_stream在IO/path间传输bytes。Copy不是atomic:中途失败可能留下partial destination。写新文件使用temp destination,完整flush/验证后rename;保留mode/owner/timestamp是否需要由contract决定。
require "fileutils"
FileUtils.cp("source.bin", "copy.bin", preserve: true)
raise "copy mismatch" unless File.size("source.bin") == File.size("copy.bin")Size相等不是内容完整证明;关键artifact用digest并注意读取期间source变化。Copy symlink本身还是target、是否跨mount、sparse file/xattr怎样处理都需选API。
删除文件
File.delete/unlink移除name,missing/permission抛异常;打开handle在Unix/Windows表现不同。避免rescue nil吞错。
批量删除先固定root与manifest,逐项记录result,支持dry-run;不要rm_rf拼接用户input。需要trash/recovery不是unlink,而是move到受控区并管理retention。
目录的操作:目录内容的读取
Dir.entries包含./..,Dir.children/each_child在支持版本排除;Ruby 2.3 availability需验证,可手工reject。返回names通常相对dir,顺序来自filesystem,不是sorted contract。保证输出可复现。
entries = Dir.entries(".")
.reject { |name| name == "." || name == ".." }
.sort
entries.each { |name| puts name }Enumerate后entry可消失/改变;逐项operation处理ENOENT。Huge directory避免一次entries materialize,使用foreach/each_child,并限制count/time。
目录的创建与删除
Dir.mkdir(path, mode)创建一层,FileUtils.mkdir_p创建parents;mode还受umask。Existing path可能是file/symlink/directory,不能统一当success。。
Dir.rmdir只删empty directory;recursive remove由FileUtils.rm_r/rm_rf,但危险面大。Required root不可为/、home或empty expansion;解析absolute/canonical policy并使用allowlisted workspace。递归删除遇symlink应删除link不跟随target,目标API需测试。
文件与目录的属性
File.stat跟随symlink,lstat查看link本身;Stat提供type、size、mode、uid/gid和times。Predicate file?、directory?、symlink?方便,但都是snapshot。不能当长期授权证据。
Permission check如readable?也可能与实际open race,且ACL/container privileges复杂;直接open并处理error。Size用于budget初筛但file可增长,读取仍需hard cap。
文件名的操作
File.join按platform separator构造,basename、dirname、extname拆name,expand_path解析relative path。不使用字符串拼"root/" + input。
expand_path消除./..但不等于解析所有symlink;realpath要求存在并解析links,仍有之后race。判断path在root内不能只用prefix(/safe2前缀问题),应比较path components/Pathname ancestry,并针对case-insensitive platform测试。
Extension不是可信file type,basename可能为空/特殊,Windows reserved names和NUL等由platform限制。Upload保存使用server-generated name,原name仅作metadata/display。
与文件操作相关的库:find、tempfile、fileutils
find库
Find.find递归yield paths,Find.prune跳过subtree。防cycle和资源耗尽。
require "find"
Find.find(root) do |path|
if File.directory?(path) && File.basename(path) == ".git"
Find.prune
else
puts path
end
endPredicate仍有race;不要跟随untrusted symlink。输出排序若要求稳定,需收集(受size上限)或分层sort;Find order不是portable promise。
tempfile库
Tempfile以unique restrictive file减少name collision,block/create形式自动cleanup更可靠。。
Atomic replace的temp应位于destination同directory/filesystem,写完flush/fsync、chmod/chown policy、rename,并必要时sync directory。Tempfile path不应泄露敏感data,异常后ensure cleanup。
fileutils库
FileUtils提供cp/mv/mkdir_p/rm_r等high-level operations,但不会替你选择trust root、conflict、rollback和durability。。
正式节点与章专属证据
- ↡在“第18章 File类与Dir类”中,变更、复制与删除文件必须连接输入、状态变化与可复核结果。 :第 1 个正式节点要能回到“路径归属、符号链接策略、权限与替换顺序在每次文件变更前明确。”,并说明故障发生前后的第一处差异。
- ↡在“第18章 File类与Dir类”中,目录内容的读取必须连接输入、状态变化与可复核结果。 :第 2 个正式节点要能回到“路径归属、符号链接策略、权限与替换顺序在每次文件变更前明确。”,并说明故障发生前后的第一处差异。
- ↡在“第18章 File类与Dir类”中,目录的创建与删除必须连接输入、状态变化与可复核结果。 :第 3 个正式节点要能回到“路径归属、符号链接策略、权限与替换顺序在每次文件变更前明确。”,并说明故障发生前后的第一处差异。
- ↡在“第18章 File类与Dir类”中,文件与目录的属性必须连接输入、状态变化与可复核结果。 :第 4 个正式节点要能回到“路径归属、符号链接策略、权限与替换顺序在每次文件变更前明确。”,并说明故障发生前后的第一处差异。
- ↡在“第18章 File类与Dir类”中,文件名的操作必须连接输入、状态变化与可复核结果。 :第 5 个正式节点要能回到“路径归属、符号链接策略、权限与替换顺序在每次文件变更前明确。”,并说明故障发生前后的第一处差异。
- ↡在“第18章 File类与Dir类”中,find库必须连接输入、状态变化与可复核结果。 :第 6 个正式节点要能回到“路径归属、符号链接策略、权限与替换顺序在每次文件变更前明确。”,并说明故障发生前后的第一处差异。
- ↡在“第18章 File类与Dir类”中,tempfile库必须连接输入、状态变化与可复核结果。 :第 7 个正式节点要能回到“路径归属、符号链接策略、权限与替换顺序在每次文件变更前明确。”,并说明故障发生前后的第一处差异。
- ↡在“第18章 File类与Dir类”中,fileutils库必须连接输入、状态变化与可复核结果。 :第 8 个正式节点要能回到“路径归属、符号链接策略、权限与替换顺序在每次文件变更前明确。”,并说明故障发生前后的第一处差异。
对象与状态模型
从输入、接收者到可观察证据
怎样让路径、目录遍历、临时文件和复制删除操作保持原子与可回退?
输入与接收者
固定变更、复制与删除文件所需的原始值、Ruby 版本和调用入口。
状态变化
在执行前记录接收者身份,并声明目录内容的读取的允许状态。
观察证据
保存第18章 File类与Dir类的初值、参数、编码或资源位置。
正式节点:变更、复制与删除文件、目录内容的读取、目录的创建与删除、文件与目录的属性、文件名的操作、find库、tempfile库、fileutils库
控制与消息轨迹
在相同初值下定位首个分岔
- 01固定变更、复制与删除文件的输入和接收者
- 02执行目录内容的读取并记录状态
- 03观察目录的创建与删除的返回或副作用
- 04用fileutils库核对不变量并复位
运行不变量:路径归属、符号链接策略、权限与替换顺序在每次文件变更前明确。
边界故障探针
一次只破坏一个前提
本章回顾:Path是变化的namespace,不是稳定capability
- Rename/copy/delete有不同atomicity、metadata、filesystem和partial failure;atomic replace使用同目录temp流程。
- Directory enumeration不保证order且entries会变化;创建/删除固定root、existing type、mode和recursion。
- Stat/lstat是否跟随symlink不同,metadata只是snapshot,operation仍处理实际error。
- Path helpers替代字符串拼接,但canonicalization不自动解决symlink race与platform差异。
- Find有traversal budget,Tempfile有lifecycle,FileUtils只封装mechanics不承担security policy。
练习与答案
练习
- 问题 1:建立正常轨迹。 回答“怎样让路径、目录遍历、临时文件和复制删除操作保持原子与可回退?”,并写出四步执行记录。
- 问题 2:注入单一故障。 只制造“先删除目标再写新文件,中途失败后同时失去旧版本和新版本”,应从哪里开始定位?
- 问题 3:覆盖正式节点。 用一个证据包串联变更、复制与删除文件、目录内容的读取、目录的创建与删除、文件与目录的属性、文件名的操作、find库、tempfile库、fileutils库,说明为什么结论可由另一位读者独立复核。
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 变更、复制与删除文件
“第18章 File类与Dir类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 目录内容的读取
“第18章 File类与Dir类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 目录的创建与删除
“第18章 File类与Dir类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 文件与目录的属性
“第18章 File类与Dir类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 文件名的操作
“第18章 File类与Dir类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- find库
“第18章 File类与Dir类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- tempfile库
“第18章 File类与Dir类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- fileutils库
“第18章 File类与Dir类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。