Ruby 文件操作完全指南:读写、目录遍历与安全处理
本文全面讲解 Ruby 文件处理:File 读写、块语法自动关闭、文件元信息、Dir 目录操作、Pathname 面向对象路径处理,以及编码、权限等生产级注意事项。
Ruby 文件操作的核心是 File 与 Dir 两个类加 Pathname 封装:读写一律用块语法(File.open(...) { |f| ... })确保句柄释放,大文件用 foreach 逐行处理避免内存爆炸,路径拼接用 File.join 或 Pathname 保证跨平台。
读文件的三种姿势
content = File.read('config.yml') # 一次性全读,适合小文件
lines = File.readlines('app.log') # 读成数组,同样全量进内存
File.foreach('big.log') do |line| # 逐行迭代,内存恒定
puts line if line.include?('ERROR')
end
原则很简单:确定文件很小才用 read,日志、CSV、数据导出这类不确定体积的一律 foreach 或 each_line。配合 lazy 还能链式处理(File.foreach(f).lazy.select {...}.first(10))。
写文件与模式
File.write('out.txt', "hello\n") # 覆盖写
File.write('out.txt', "more\n", mode: 'a') # 追加
File.open('data.csv', 'w') do |f| # 块语法:自动关闭
f.puts 'name,age'
f.puts 'Alice,30'
end
常用模式:'r' 只读、'w' 覆盖写(不存在则创建)、'a' 追加、'r+' 读写。块语法是硬性习惯——手动 open/close 一旦中途抛异常,句柄就泄漏了。
块语法为什么重要
File.open('report.txt', 'w') do |f|
raise '写入中途出错'
end
# 即使抛异常,文件也会被正确关闭
等价的手动写法需要 begin/ensure 才能保证关闭,块语法把这件事变成了默认行为。
文件信息与元数据
File.exist?('a.txt') # 是否存在
File.size('a.txt') # 字节数
File.mtime('a.txt') # 修改时间
File.file?('a.txt') # 是普通文件
File.directory?('logs') # 是目录
File.executable?('run.sh')
File.extname('a.tar.gz') # => ".gz"
File.basename('/x/y/a.txt') # => "a.txt"
File.dirname('/x/y/a.txt') # => "/x/y"
目录操作与路径处理
Dir.mkdir('logs') # 建单层目录
FileUtils.mkdir_p('logs/2026/10') # 递归建目录
Dir.entries('.') # 目录内容(含 . 和 ..)
Dir.glob('**/*.rb') # 递归匹配
Dir.children('tmp').each { |f| ... } # 更干净的遍历
路径拼接别用字符串插值,File.join('logs', 'app.log') 会自动处理分隔符;复杂路径操作用 Pathname:
require 'pathname'
Pathname('data/reports') + '2026-10.csv' # => #<Pathname:data/reports/2026-10.csv>
Pathname('a.txt').expand_path # 绝对路径
错误处理与安全实践
文件操作必须考虑异常:Errno::ENOENT(不存在)、Errno::EACCES(无权限)、Errno::ENOSPC(磁盘满)。处理用户上传或外部输入的路径时,警惕路径穿越:"../../etc/passwd" 这类输入要先用 File.expand_path 展开再校验是否落在允许目录内。
临时文件用标准库 Tempfile(自动清理、唯一命名),CSV 用 csv 库而不是自己 split,二进制读写记得模式加 'b'。
常见问题(FAQ)
Q:读取时中文乱码怎么办?
显式指定编码:File.read('f.txt', encoding: 'UTF-8')。来源是 GBK 的老文件用 encoding: 'GBK:UTF-8' 边读边转码。
Q:怎么实现"读大文件最后 100 行"?
不要全读。简单方案是调系统命令 IO.popen("tail -n 100 f.log");纯 Ruby 方案是从文件末尾反向 seek 分块读取,或直接用 File.foreach 配合环形缓冲区只保留最后 N 行。
Q:Dir.glob 的 ** 和 * 区别是什么?
* 匹配单层,** 递归匹配任意层。Dir.glob('log/**/*.log') 找出 log 下所有层级的 .log 文件;注意大目录下递归 glob 会把结果一次性收集进数组,超大目录改用 Find.find 流式遍历。
参考文档
本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。