Skip to content

Ruby 文件处理及I/O

文件处理是编程中的基本操作之一,Ruby提供了强大而灵活的文件I/O功能。无论是读取配置文件、处理日志、操作数据文件,还是进行文件系统操作,Ruby都提供了丰富的内置方法来简化这些任务。本章将详细介绍Ruby中文件处理和I/O操作的各种方法和最佳实践。

🎯 文件基础操作

文件打开和关闭

ruby
# 基本文件打开方式
file = File.open("example.txt", "r")
content = file.read
file.close

# 使用块自动关闭文件(推荐方式)
File.open("example.txt", "r") do |file|
  content = file.read
  puts content
end  # 文件自动关闭

# 不同的文件模式
# "r"  - 只读模式(默认)
# "w"  - 写入模式(覆盖文件内容)
# "a"  - 追加模式
# "r+" - 读写模式
# "w+" - 读写模式(覆盖文件内容)
# "a+" - 读写模式(追加)

# 指定编码
File.open("example.txt", "r:utf-8") do |file|
  content = file.read
  puts content
end

文件读取操作

ruby
# 读取整个文件内容
File.open("example.txt", "r") do |file|
  content = file.read
  puts content
end

# 读取文件的指定字节数
File.open("example.txt", "r") do |file|
  chunk = file.read(100)  # 读取前100个字节
  puts chunk
end

# 逐行读取文件
File.open("example.txt", "r") do |file|
  file.each_line do |line|
    puts "行: #{line.chomp}"  # chomp移除换行符
  end
end

# 读取所有行到数组
File.open("example.txt", "r") do |file|
  lines = file.readlines
  puts lines.inspect
end

# 读取单行
File.open("example.txt", "r") do |file|
  first_line = file.gets
  puts "第一行: #{first_line}"
end

# 使用File.read快捷方法
content = File.read("example.txt")
puts content

# 使用File.readlines快捷方法
lines = File.readlines("example.txt")
puts lines.inspect

✍️ 文件写入操作

基本写入操作

ruby
# 覆盖写入文件
File.open("output.txt", "w") do |file|
  file.write("Hello, World!\n")
  file.write("这是第二行\n")
end

# 使用puts写入(自动添加换行符)
File.open("output.txt", "w") do |file|
  file.puts("Hello, World!")
  file.puts("这是第二行")
end

# 追加写入文件
File.open("output.txt", "a") do |file|
  file.puts("这是追加的内容")
end

# 使用File.write快捷方法
File.write("output.txt", "Hello, World!\n", mode: "w")

# 追加写入
File.write("output.txt", "追加内容\n", mode: "a")

# 格式化写入
data = { name: "张三", age: 25, city: "北京" }
File.open("user.txt", "w") do |file|
  file.puts "用户信息:"
  data.each { |key, value| file.puts "#{key}: #{value}" }
end

高级写入操作

ruby
# 写入二进制文件
image_data = File.read("input.jpg", mode: "rb")
File.open("output.jpg", "wb") do |file|
  file.write(image_data)
end

# 使用print方法写入
File.open("output.txt", "w") do |file|
  file.print("Hello")
  file.print(" World!")  # 不添加换行符
  file.puts  # 添加换行符
end

# 写入数组内容
lines = ["第一行", "第二行", "第三行"]
File.open("output.txt", "w") do |file|
  lines.each { |line| file.puts(line) }
end

# 使用<<操作符
File.open("output.txt", "w") do |file|
  file << "Hello, World!\n"
  file << "这是第二行\n"
end

# 写入哈希数据
config = {
  database: { host: "localhost", port: 5432 },
  logging: { level: "info", file: "app.log" }
}

File.open("config.txt", "w") do |file|
  config.each do |section, settings|
    file.puts "[#{section}]"
    settings.each { |key, value| file.puts "#{key} = #{value}" }
    file.puts
  end
end

📁 文件系统操作

文件和目录检查

ruby
# 检查文件是否存在
puts File.exist?("example.txt")  # true/false
puts File.exist?("nonexistent.txt")  # false

# 检查是否为文件
puts File.file?("example.txt")  # true/false

# 检查是否为目录
puts File.directory?("docs")  # true/false

# 检查文件可读性
puts File.readable?("example.txt")  # true/false

# 检查文件可写性
puts File.writable?("example.txt")  # true/false

# 检查文件可执行性
puts File.executable?("script.rb")  # true/false

# 获取文件大小
puts File.size("example.txt")  # 字节数

# 检查文件是否为空
puts File.zero?("empty.txt")  # true/false

# 获取文件修改时间
puts File.mtime("example.txt")  # 2023-12-25 14:30:45 +0800

# 获取文件创建时间
puts File.ctime("example.txt")  # 2023-12-25 14:30:45 +0800

# 获取文件访问时间
puts File.atime("example.txt")  # 2023-12-25 14:30:45 +0800

目录操作

ruby
require 'fileutils'

# 列出目录内容
puts Dir.entries(".")  # 当前目录的所有文件和目录

# 获取当前工作目录
puts Dir.pwd  # D:/Workspace/Coding/...

# 更改当前工作目录
Dir.chdir("docs") do
  puts Dir.pwd  # 更改后的目录
  # 在此目录中执行操作
end

# 创建目录
Dir.mkdir("new_directory")

# 创建多级目录
FileUtils.mkdir_p("parent/child/grandchild")

# 删除空目录
Dir.rmdir("empty_directory")

# 删除目录及其内容
FileUtils.rm_rf("directory_to_delete")

# 复制目录
FileUtils.cp_r("source_directory", "destination_directory")

# 移动目录
FileUtils.mv("old_directory", "new_directory")

文件操作

ruby
require 'fileutils'

# 复制文件
FileUtils.cp("source.txt", "destination.txt")

# 复制多个文件
FileUtils.cp(["file1.txt", "file2.txt"], "destination_directory")

# 移动文件
FileUtils.mv("old_name.txt", "new_name.txt")

# 删除文件
FileUtils.rm("unwanted.txt")

# 删除多个文件
FileUtils.rm(["file1.txt", "file2.txt"])

# 安全删除(忽略不存在的文件)
FileUtils.rm_f("maybe_exists.txt")

# 强制删除(不提示确认)
FileUtils.rm_rf("directory_or_file")

# 创建硬链接
FileUtils.ln("source.txt", "hard_link.txt")

# 创建符号链接
FileUtils.ln_s("source.txt", "soft_link.txt")

# 更改文件权限
FileUtils.chmod(0644, "file.txt")

# 更改文件所有者(需要管理员权限)
# FileUtils.chown("user", "group", "file.txt")

# 更改文件时间戳
FileUtils.touch("file.txt")

🔍 文件查找和遍历

Glob模式匹配

ruby
# 查找所有.rb文件
ruby_files = Dir.glob("*.rb")
puts ruby_files.inspect

# 递归查找所有.rb文件
all_ruby_files = Dir.glob("**/*.rb")
puts all_ruby_files.inspect

# 查找特定目录下的文件
docs_files = Dir.glob("docs/*")
puts docs_files.inspect

# 使用多种模式
mixed_files = Dir.glob(["*.rb", "*.md", "config/*.yml"])
puts mixed_files.inspect

# 查找隐藏文件
hidden_files = Dir.glob(".*")
puts hidden_files.inspect

# 查找特定扩展名的文件
image_files = Dir.glob("*.{jpg,png,gif}")
puts image_files.inspect

# 查找数字命名的文件
numbered_files = Dir.glob("[0-9]*.txt")
puts numbered_files.inspect

目录遍历

ruby
# 递归遍历目录
def traverse_directory(dir)
  Dir.foreach(dir) do |entry|
    next if entry == "." || entry == ".."
    
    path = File.join(dir, entry)
    if File.directory?(path)
      puts "目录: #{path}"
      traverse_directory(path)  # 递归遍历子目录
    else
      puts "文件: #{path}"
    end
  end
end

# traverse_directory(".")

# 使用Find模块遍历
require 'find'

Find.find(".") do |path|
  if FileTest.directory?(path)
    puts "目录: #{path}"
  else
    puts "文件: #{path}"
  end
end

# 按深度遍历
def traverse_by_depth(dir, depth = 0)
  indent = "  " * depth
  Dir.foreach(dir) do |entry|
    next if entry == "." || entry == ".."
    
    path = File.join(dir, entry)
    if File.directory?(path)
      puts "#{indent}目录: #{entry}"
      traverse_by_depth(path, depth + 1)
    else
      puts "#{indent}文件: #{entry}"
    end
  end
end

# traverse_by_depth(".")

🎯 实用文件处理示例

配置文件处理器

ruby
class ConfigFileHandler
  # 读取简单的键值对配置文件
  def self.read_config(filename)
    config = {}
    File.open(filename, "r") do |file|
      file.each_line do |line|
        line = line.strip
        next if line.empty? || line.start_with?("#")  # 跳过空行和注释
        
        if line.include?("=")
          key, value = line.split("=", 2).map(&:strip)
          config[key] = value
        end
      end
    end
    config
  end
  
  # 写入键值对配置文件
  def self.write_config(filename, config, comments = {})
    File.open(filename, "w") do |file|
      config.each do |key, value|
        file.puts "# #{comments[key]}" if comments[key]
        file.puts "#{key} = #{value}"
        file.puts
      end
    end
  end
  
  # 更新配置文件
  def self.update_config(filename, updates)
    config = read_config(filename)
    config.merge!(updates)
    write_config(filename, config)
  end
end

# 使用配置文件处理器
# config = ConfigFileHandler.read_config("app.conf")
# puts config.inspect

# new_config = {
#   "database_host" => "localhost",
#   "database_port" => "5432",
#   "log_level" => "info"
# }
# 
# comments = {
#   "database_host" => "数据库主机地址",
#   "database_port" => "数据库端口",
#   "log_level" => "日志级别"
# }
# 
# ConfigFileHandler.write_config("app.conf", new_config, comments)

日志文件处理器

ruby
class LogFileHandler
  def initialize(log_file)
    @log_file = log_file
  end
  
  # 写入日志
  def log(level, message)
    timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
    log_entry = "[#{timestamp}] #{level.upcase}: #{message}\n"
    
    File.open(@log_file, "a") do |file|
      file.write(log_entry)
    end
  end
  
  # 读取最近的日志条目
  def recent_entries(count = 10)
    return [] unless File.exist?(@log_file)
    
    lines = File.readlines(@log_file)
    lines.last(count)
  end
  
  # 按级别过滤日志
  def filter_by_level(level)
    return [] unless File.exist?(@log_file)
    
    File.readlines(@log_file).select do |line|
      line.include?("[#{level.upcase}]")
    end
  end
  
  # 清空日志文件
  def clear
    File.open(@log_file, "w") { |file| file.truncate(0) }
  end
  
  # 获取日志文件大小
  def size
    File.size(@log_file) if File.exist?(@log_file)
  end
  
  # 轮转日志文件
  def rotate(max_size = 1024 * 1024)  # 1MB
    return unless File.exist?(@log_file)
    return if File.size(@log_file) < max_size
    
    # 重命名当前日志文件
    timestamp = Time.now.strftime("%Y%m%d_%H%M%S")
    backup_name = "#{@log_file}.#{timestamp}"
    File.rename(@log_file, backup_name)
    
    # 创建新的日志文件
    File.open(@log_file, "w") { |file| file.write("# 新的日志文件\n") }
  end
end

# 使用日志文件处理器
# logger = LogFileHandler.new("app.log")
# logger.log("info", "应用程序启动")
# logger.log("error", "发生错误")
# logger.log("debug", "调试信息")
# 
# puts "最近的日志:"
# logger.recent_entries(5).each { |entry| puts entry }
# 
# puts "错误日志:"
# logger.filter_by_level("error").each { |entry| puts entry }

CSV文件处理器

ruby
require 'csv'

class CSVHandler
  # 读取CSV文件
  def self.read_csv(filename)
    data = []
    CSV.foreach(filename, headers: true) do |row|
      data << row.to_h
    end
    data
  end
  
  # 写入CSV文件
  def self.write_csv(filename, data, headers = nil)
    headers ||= data.first.keys if data.first.is_a?(Hash)
    
    CSV.open(filename, "w") do |csv|
      csv << headers
      data.each do |row|
        if row.is_a?(Hash)
          csv << headers.map { |header| row[header] }
        else
          csv << row
        end
      end
    end
  end
  
  # 追加数据到CSV文件
  def self.append_to_csv(filename, row)
    CSV.open(filename, "a") do |csv|
      csv << row
    end
  end
  
  # 按条件筛选CSV数据
  def self.filter_csv(filename, &block)
    filtered_data = []
    CSV.foreach(filename, headers: true) do |row|
      filtered_data << row.to_h if block.call(row.to_h)
    end
    filtered_data
  end
  
  # CSV数据统计
  def self.csv_stats(filename, column)
    values = []
    CSV.foreach(filename, headers: true) do |row|
      values << row[column]
    end
    
    {
      count: values.length,
      unique_count: values.uniq.length,
      min: values.min,
      max: values.max
    }
  end
end

# 使用CSV处理器
# data = [
#   { "姓名" => "张三", "年龄" => "25", "城市" => "北京" },
#   { "姓名" => "李四", "年龄" => "30", "城市" => "上海" },
#   { "姓名" => "王五", "年龄" => "28", "城市" => "广州" }
# ]
# 
# CSVHandler.write_csv("users.csv", data)
# 
# read_data = CSVHandler.read_csv("users.csv")
# puts read_data.inspect
# 
# filtered = CSVHandler.filter_csv("users.csv") { |row| row["年龄"].to_i > 25 }
# puts filtered.inspect

📊 文件处理性能优化

大文件处理

ruby
# 逐行处理大文件
def process_large_file(filename)
  line_count = 0
  File.open(filename, "r") do |file|
    file.each_line do |line|
      line_count += 1
      # 处理每一行
      process_line(line)
    end
  end
  line_count
end

def process_line(line)
  # 处理单行数据
  puts "处理行: #{line.chomp}"
end

# 使用缓冲区处理大文件
def process_large_file_with_buffer(filename, buffer_size = 1024)
  File.open(filename, "r") do |file|
    while buffer = file.read(buffer_size)
      # 处理缓冲区数据
      process_buffer(buffer)
    end
  end
end

def process_buffer(buffer)
  # 处理缓冲区数据
  puts "处理缓冲区: #{buffer.length} 字节"
end

# 分块处理文件
def process_file_in_chunks(filename, chunk_size = 1000)
  File.open(filename, "r") do |file|
    chunk = []
    file.each_line do |line|
      chunk << line.chomp
      if chunk.length >= chunk_size
        process_chunk(chunk)
        chunk = []
      end
    end
    # 处理最后一个块
    process_chunk(chunk) unless chunk.empty?
  end
end

def process_chunk(chunk)
  # 处理数据块
  puts "处理块: #{chunk.length} 行"
end

文件流处理

ruby
# 使用IO流处理
class FileStreamProcessor
  def self.process_stream(input_stream, output_stream, &block)
    input_stream.each_line do |line|
      processed_line = block.call(line)
      output_stream.write(processed_line)
    end
  end
  
  # 文件到文件的流处理
  def self.process_file_to_file(input_file, output_file, &block)
    File.open(input_file, "r") do |input|
      File.open(output_file, "w") do |output|
        process_stream(input, output, &block)
      end
    end
  end
  
  # 文件到标准输出的流处理
  def self.process_file_to_stdout(input_file, &block)
    File.open(input_file, "r") do |input|
      process_stream(input, STDOUT, &block)
    end
  end
end

# 使用流处理器
# FileStreamProcessor.process_file_to_file("input.txt", "output.txt") do |line|
#   line.upcase
# end

🛡️ 文件处理安全最佳实践

安全文件操作

ruby
class SafeFileHandler
  # 安全读取文件
  def self.safe_read(filename, max_size = 10 * 1024 * 1024)  # 10MB限制
    return nil unless File.exist?(filename)
    return nil if File.size(filename) > max_size
    
    File.read(filename)
  rescue => e
    puts "读取文件出错: #{e.message}"
    nil
  end
  
  # 安全写入文件
  def self.safe_write(filename, content, max_size = 10 * 1024 * 1024)
    return false if content.length > max_size
    
    File.write(filename, content)
    true
  rescue => e
    puts "写入文件出错: #{e.message}"
    false
  end
  
  # 验证文件路径安全性
  def self.safe_path?(path, base_dir = Dir.pwd)
    # 解析绝对路径
    abs_path = File.expand_path(path, base_dir)
    base_abs_path = File.expand_path(base_dir)
    
    # 检查是否在基础目录内
    abs_path.start_with?(base_abs_path)
  end
  
  # 安全删除文件
  def self.safe_delete(filename)
    return false unless File.exist?(filename)
    
    File.delete(filename)
    true
  rescue => e
    puts "删除文件出错: #{e.message}"
    false
  end
  
  # 备份文件
  def self.backup_file(filename)
    return false unless File.exist?(filename)
    
    backup_name = "#{filename}.backup.#{Time.now.to_i}"
    FileUtils.cp(filename, backup_name)
    backup_name
  rescue => e
    puts "备份文件出错: #{e.message}"
    false
  end
end

# 使用安全文件处理器
# if SafeFileHandler.safe_path?("data.txt", "/safe/directory")
#   content = SafeFileHandler.safe_read("data.txt")
#   puts content
# end

# success = SafeFileHandler.safe_write("output.txt", "Hello, World!")
# puts "写入成功: #{success}"

# backup = SafeFileHandler.backup_file("important.txt")
# puts "备份文件: #{backup}"

文件权限和所有权

ruby
class FilePermissionManager
  # 检查文件权限
  def self.check_permissions(filename)
    {
      readable: File.readable?(filename),
      writable: File.writable?(filename),
      executable: File.executable?(filename),
      owned: File.owned?(filename),
      owned_by_group: File.grpowned?(filename)
    }
  end
  
  # 设置文件权限(Unix/Linux系统)
  def self.set_permissions(filename, permissions)
    File.chmod(permissions, filename)
  rescue => e
    puts "设置权限出错: #{e.message}"
  end
  
  # 获取文件详细信息
  def self.file_info(filename)
    stat = File.stat(filename)
    {
      size: stat.size,
      mtime: stat.mtime,
      ctime: stat.ctime,
      atime: stat.atime,
      uid: stat.uid,
      gid: stat.gid,
      mode: "%o" % stat.mode,
      readable: stat.readable?,
      writable: stat.writable?,
      executable: stat.executable?
    }
  rescue => e
    puts "获取文件信息出错: #{e.message}"
    nil
  end
end

# 使用文件权限管理器
# permissions = FilePermissionManager.check_permissions("example.txt")
# puts permissions.inspect

# FilePermissionManager.set_permissions("example.txt", 0644)

# info = FilePermissionManager.file_info("example.txt")
# puts info.inspect

🎯 文件处理最佳实践

1. 资源管理

ruby
# 始终使用块语法确保文件正确关闭
# 推荐
File.open("example.txt", "r") do |file|
  content = file.read
  # 处理内容
end  # 文件自动关闭

# 不推荐
file = File.open("example.txt", "r")
content = file.read
# 忘记关闭文件
# file.close

# 使用ensure确保资源清理
def manual_file_handling
  file = File.open("example.txt", "r")
  begin
    content = file.read
    # 处理内容
  ensure
    file.close
  end
end

2. 错误处理

ruby
# 处理文件操作异常
def robust_file_operation(filename)
  File.open(filename, "r") do |file|
    content = file.read
    process_content(content)
  end
rescue Errno::ENOENT
  puts "文件 #{filename} 不存在"
rescue Errno::EACCES
  puts "没有权限访问文件 #{filename}"
rescue => e
  puts "处理文件时出错: #{e.message}"
end

# 检查文件存在性
def safe_file_read(filename)
  unless File.exist?(filename)
    puts "文件 #{filename} 不存在"
    return nil
  end
  
  File.read(filename)
rescue => e
  puts "读取文件出错: #{e.message}"
  nil
end

# 处理编码问题
def read_with_encoding(filename, encoding = "UTF-8")
  File.open(filename, "r:#{encoding}") do |file|
    file.read
  end
rescue Encoding::InvalidByteSequenceError
  puts "文件编码不正确"
rescue => e
  puts "读取文件时出错: #{e.message}"
end

3. 性能优化

ruby
# 对于大文件,使用流式处理
def process_large_file_efficiently(filename)
  File.open(filename, "r") do |file|
    file.each_line do |line|
      # 处理每一行,而不是读取整个文件
      process_line(line)
    end
  end
end

# 批量操作文件
def batch_file_operations(filenames)
  filenames.each do |filename|
    begin
      process_file(filename)
    rescue => e
      puts "处理文件 #{filename} 时出错: #{e.message}"
    end
  end
end

# 使用临时文件
def process_with_temp_file(data)
  temp_file = Tempfile.new("processing")
  begin
    temp_file.write(data)
    temp_file.close
    # 处理临时文件
    result = process_file(temp_file.path)
    result
  ensure
    temp_file.unlink  # 删除临时文件
  end
end

4. 实际应用场景

ruby
# 日志分析器
class LogAnalyzer
  def initialize(log_file)
    @log_file = log_file
  end
  
  def analyze
    stats = {
      total_lines: 0,
      error_count: 0,
      warning_count: 0,
      info_count: 0,
      unique_ips: Set.new,
      requests_by_hour: Hash.new(0)
    }
    
    File.open(@log_file, "r") do |file|
      file.each_line do |line|
        stats[:total_lines] += 1
        
        # 解析日志行
        if parsed_data = parse_log_line(line)
          # 统计不同级别的日志
          case parsed_data[:level]
          when "ERROR"
            stats[:error_count] += 1
          when "WARNING"
            stats[:warning_count] += 1
          when "INFO"
            stats[:info_count] += 1
          end
          
          # 收集唯一IP
          stats[:unique_ips] << parsed_data[:ip] if parsed_data[:ip]
          
          # 按小时统计请求
          if parsed_data[:timestamp]
            hour = parsed_data[:timestamp].hour
            stats[:requests_by_hour][hour] += 1
          end
        end
      end
    end
    
    stats
  end
  
  private
  
  def parse_log_line(line)
    # 简化的日志解析
    # 实际应用中可能需要更复杂的正则表达式
    {
      timestamp: Time.now,  # 简化处理
      level: line.include?("ERROR") ? "ERROR" : 
             line.include?("WARNING") ? "WARNING" : "INFO",
      ip: line.match(/\d+\.\d+\.\d+\.\d+/)&.to_s,
      message: line
    }
  end
end

# 数据导入器
class DataImporter
  def self.import_csv_to_database(csv_file, database)
    imported_count = 0
    failed_count = 0
    
    CSV.foreach(csv_file, headers: true) do |row|
      begin
        # 转换数据
        record = convert_row_to_record(row)
        
        # 插入数据库
        database.insert(record)
        imported_count += 1
      rescue => e
        puts "导入行失败: #{e.message}"
        failed_count += 1
      end
    end
    
    {
      imported: imported_count,
      failed: failed_count,
      total: imported_count + failed_count
    }
  end
  
  private
  
  def self.convert_row_to_record(row)
    # 根据需要转换数据
    row.to_h
  end
end

📚 下一步学习

掌握了Ruby文件处理和I/O操作后,建议继续学习:

继续您的Ruby学习之旅吧!

本站内容仅供学习和研究使用。