Ruby 零基础教程

Ruby 文件写入

在许多应用程序中,将信息持久保存下来的能力与读取信息的能力同样重要。上一章我们重点学习了如何从文件中读取现有数据,本章将深入探讨同样重要的技能:将数据写入文件

无论你是要保存用户偏好设置、记录应用程序事件,还是导出数据报表,写入文件都能让你的 Ruby 程序与外部世界进行交互,创建和修改在程序运行结束后依然存在的数据。这种能力是许多实际应用的基础,从小巧的实用脚本到复杂的数据管理系统都离不开它。

1. 打开文件准备写入

在写入任何数据之前,你首先需要以允许写入的“模式”打开文件。我们在读取文件时接触过的 File.open 方法同样用于写入,只是需要传入不同的模式参数。理解这些模式至关重要,因为它们决定了你的程序如何与文件交互,尤其是如何处理文件中已有的内容。

最常用的写入模式有以下两种:

  • 'w' (写入模式 / Write mode):此模式用于向文件写入全新内容。如果指定的文件不存在,Ruby 会自动创建它。如果文件已经存在,它的全部原有内容将被截断(清空),Ruby 会从一个空文件开始重新写入。这是一个破坏性的操作,请务必谨慎使用!
  • 'a' (追加模式 / Append mode):此模式用于将新内容添加到文件的末尾。如果指定的文件不存在,Ruby 会创建它。如果文件已经存在,你写入的任何新数据都会被追加到当前内容的末尾,从而保留原有数据。这非常适合用于记录日志或在不覆盖数据的情况下添加新条目。

最佳实践:就像读取文件时一样,强烈建议在使用 File.open 写入文件时配合代码块 (do...end) 使用。这能确保即使发生错误,Ruby 也会在代码块执行完毕后自动关闭文件,从而防止潜在的资源泄漏或数据损坏。

1.1 使用写入模式 ('w')

让我们看看如何以写入模式打开文件并添加一些内容。记住,这会覆盖文件中的任何现有内容。

# 示例 1:创建一个新文件或覆盖一个现有文件

# 定义文件名
filename = "my_first_output.txt"

# 以写入模式 ('w') 并配合代码块打开文件
# 当执行离开代码块时,该代码块会自动关闭文件。
File.open(filename, 'w') do |file|
  # 代码块内部的 'file' 对象代表已打开的文件。
  file.write("你好,Ruby 的世界!\n") # 向文件写入一个字符串,包含一个换行符。
  file.write("这是我写入文件的第一行。\n")
  file.write("让我们再加一行。\n")
end

puts "内容已在写入模式下保存到 '#{filename}' 中。"

# 为了验证,你可以尝试读取该文件(正如在上一章学到的那样)
# 或者在运行此脚本后直接手动打开该文件查看。
# File.open(filename, 'r') do |file|
#   puts "\n--- '#{filename}' 的内容: ---"
#   puts file.read
#   puts "----------------------------------"
# end

如果 my_first_output.txt 之前不存在,它会被创建。如果它已经存在,它之前的内容将被清空并替换为我们刚刚写入的三行文本。

1.2 使用追加模式 ('a')

现在,让我们探索一下追加模式。使用这种模式可以安全地添加数据,而不用担心丢失现有信息。

# 示例 2:向文件追加内容

# 定义文件名(为了演示,我们使用同一个文件)
filename = "my_first_output.txt"

# 首先,让我们确保文件有一些初始内容(或者创建它)
File.open(filename, 'w') do |file|
  file.write("用于追加演示的初始内容。\n")
end
puts "初始内容已写入 '#{filename}'。"

# 现在,以追加模式 ('a') 打开文件
File.open(filename, 'a') do |file|
  file.write("这一行是被追加进来的。\n") # 新数据将被添加在现有内容之后。
  file.write("这里是另一行追加的内容。\n")
  file.write("追加一些数字:#{123 + 456}\n") # 你可以写入插值字符串。
end
puts "额外内容已追加到 '#{filename}' 中。"

# 你可以通过重新读取来验证内容
# File.open(filename, 'r') do |file|
#   puts "\n--- 追加后 '#{filename}' 的内容: ---"
#   puts file.read
#   puts "-------------------------------------------------"
# end

运行示例 2 后,my_first_output.txt 将包含:

用于追加演示的初始内容。
这一行是被追加进来的。
这里是另一行追加的内容。
追加一些数字:579

请注意,write 方法不会在每个字符串末尾自动添加换行符 (\n)。如果你希望每条数据都独占一行,你必须在字符串中明确包含 \n

2. 使用 write 与 puts 写入数据

Ruby 提供了几种将数据写入文件对象的方法。最常见的是 writeputs。理解它们之间的区别是控制输出格式的关键。

2.1 write 方法

write 方法只是将给定的字符串直接写入文件的当前位置。它不会自动添加换行符。这为你提供了对输出格式的精确控制。你可以写入任何字符串,如果需要换行,你必须自己添加。如果传递给 write 的是非字符串对象,它们会首先使用自身的 to_s 方法被转换为字符串。

# 示例 3:使用 'write' 方法

filename = "write_example.txt"

File.open(filename, 'w') do |file|
  file.write("第 1 行,没有显式的换行符。") # 没有添加换行符
  file.write("第 2 行紧紧跟在第 1 行后面。\n") # 在这里添加了换行符
  file.write("第 3 行从新的一行开始。\n")
  
  # 写入数字 —— 它们会自动被转换为字符串。
  price = 29.99
  quantity = 3
  file.write("总价:#{price * quantity}\n") # 字符串插值会生成字符串
  file.write(100.to_s + "\n") # 显式类型转换也可以
end

puts "已使用 'write' 将内容写入 '#{filename}'。"

# write_example.txt 中的输出将会是:
# 第 1 行,没有显式的换行符。第 2 行紧紧跟在第 1 行后面。
# 第 3 行从新的一行开始。
# 总价:89.97
# 100

2.2 puts 方法

puts 方法("put string" 的缩写)与用于在控制台打印输出的 puts 非常相似。当与文件对象一起使用时,它将给定的字符串(或转换为字符串的对象)写入文件,然后自动在每个项目的末尾追加一个换行符 (\n)。这使得在编写要求每个项目占据一行的文本时非常方便。

# 示例 4:使用 'puts' 方法

filename = "puts_example.txt"

File.open(filename, 'w') do |file|
  file.puts "这是第一行。" # 'puts' 会自动添加一个换行符
  file.puts "这是第二行。" # 又一个换行符
  file.puts "还有第三行。"
  
  # 'puts' 也可以接收多个参数,每个参数都会被写在新的一行
  file.puts "项目 A", "项目 B", "项目 C"
  
  # 数字同样会被转换为字符串并自带换行符
  file.puts 42
  file.puts Math::PI
end

puts "已使用 'puts' 将内容写入 '#{filename}'。"

# puts_example.txt 中的输出将会是:
# 这是第一行。
# 这是第二行。
# 还有第三行。
# 项目 A
# 项目 B
# 项目 C
# 42
# 3.141592653589793

2.3 对比 write 和 puts

  • write:提供细粒度的控制;没有自动换行。当你需要精确的格式控制(例如写入一行的一部分或写入二进制数据)时使用。
  • puts:方便写入整行文本;自动添加换行符。用于典型的文本文件输出,即每段数据都应该拥有自己独立的一行。

3. 实际应用场景

让我们通过一些常见的场景,将这些概念付诸实践。

3.1 场景一:记录应用日志 (Logging)

假设你正在构建一个简单的应用程序,你希望记录下重要的事件日志,比如用户登录或发生错误的时间。追加模式 ('a') 非常适合这个需求,因为你绝对不想擦除过去的日志记录。

# 场景 1:简单的应用程序日志记录

def log_event(message, log_file="application.log")
  timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
  log_entry = "[#{timestamp}] #{message}\n" # 准备日志条目
  
  # 以追加模式 ('a') 打开日志文件
  File.open(log_file, 'a') do |file|
    file.write(log_entry) # 写入日志条目
  end
  puts "已记录日志:#{message}"
end

# 模拟一些应用程序事件
log_event("用户 'alice' 已登录。")
log_event("试图访问被禁止的资源。")
log_event("用户 'bob' 注册成功。")
log_event("错误:数据库连接失败。")

puts "\n请检查 'application.log' 查看详情。"

# 重新读取日志文件以演示内容的示例
# puts "\n--- 'application.log' 的内容: ---"
# File.open("application.log", 'r') { |file| puts file.read }
# puts "------------------------------------"

每次调用 log_event 时,都会有一条带有时间戳的新消息被追加到 application.log 文件中。

3.2 场景二:保存列表数据

基于我们在前面模块学过的数组知识,假设我们有一个购物清单列表,我们希望将它们保存到一个文件中,每个项目占一行。

# 场景 2:将数组中的项目保存到文件

shopping_list = [
  "苹果 (Apples)",
  "牛奶 (Milk)",
  "面包 (Bread)",
  "鸡蛋 (Eggs)",
  "奶酪 (Cheese)"
]

output_filename = "shopping_list.txt"

# 以写入模式 ('w') 打开文件,以便每次生成一个新的列表
File.open(output_filename, 'w') do |file|
  shopping_list.each do |item|
    file.puts item # 使用 puts 自动为每个项目添加换行符
  end
end

puts "购物清单已保存到 '#{output_filename}'。"

# 重新读取购物清单的示例
# puts "\n--- '#{output_filename}' 的内容: ---"
# File.open(output_filename, 'r') { |file| puts file.read }
# puts "---------------------------------------"

这将会创建 shopping_list.txt,并且每个项目都在新的一行。如果你多次运行这个脚本,shopping_list.txt 每次都会被当前的 shopping_list 数组内容所覆盖。

3.3 场景三:存储基础配置数据

有时候你可能想要保存简单的键值对配置信息。虽然在实际开发中通常会使用更复杂的格式,如 YAML 或 JSON(这可能会在后续的“Gem 与库简介”课程中涉及),但对于简单的需求,我们可以使用基础的文本格式。

# 场景 3:保存简单的配置设置

config_settings = {
  "username" => "admin",
  "theme" => "dark",
  "notifications" => "true",
  "items_per_page" => 25
}

config_filename = "app_config.txt"

File.open(config_filename, 'w') do |file|
  config_settings.each do |key, value|
    # 将每个设置格式化为 "key=value" 并附带换行符写入
    file.puts "#{key}=#{value}"
  end
end

puts "应用程序配置已保存到 '#{config_filename}'。"

# 重新读取配置设置的示例
# puts "\n--- '#{config_filename}' 的内容: ---"
# File.open(config_filename, 'r') do |file|
#   file.each_line do |line|
#     puts line.strip # strip 用于移除末尾的换行符
#   end
# end
# puts "---------------------------------------"

这会生成 app_config.txt,其中每一行代表一个设置,例如:

username=admin
theme=dark
notifications=true
items_per_page=25

这些示例展示了向文件写入数据的基础技术,它们是构建更复杂的文件操作和数据持久化功能的重要基础。