Skip to content

Lua 文件 I/O

Lua 的 I/O(输入/输出)库提供了用于读取和写入文件的函数。它提供了两种主要的文件操作模型:隐式文件描述符(使用默认的输入/输出文件)和显式文件描述符(使用特定的文件对象)。

对于下面的示例,我们假设有一个名为 data.txt 的示例文件,其内容如下:

Hello Lua Learner!
This is the second line.
End of file.

在运行写入该文件的示例之前,请确保您已备份文件,或同意对其进行修改/覆盖。

打开文件主要使用的函数是 io.open(filename [, mode])。

  • filename:要打开的文件名(或路径)。
  • mode:指定文件访问模式的字符串。如果省略,默认模式取决于 Lua 版本和操作系统,但通常是只读的。

常见的文件模式:

模式描述
"r"读取模式(默认)。文件必须存在。从文件开头开始读取。
"w"写入模式。创建新文件,或将现有文件截断为零长度。从文件开头开始写入。
"a"追加模式。如果文件不存在则创建新文件。写入总是从文件末尾开始。
"r+"更新模式(读写)。文件必须存在。读写可以在任何位置进行。
"w+"更新模式(读写)。创建新文件,或将现有文件截断为零长度。读写可以在任何位置进行。
"a+"追加更新模式(读写)。如果文件不存在则创建新文件。读取可以在任何位置进行,写入总是追加到文件末尾。

你也可以在模式字符串后附加 "b"(例如 "rb"、"wb")来以二进制模式打开文件。这在某些系统(如 Windows)上很重要,可以防止换行符转换。在类 Unix 系统上,二进制模式和文本模式通常是一样的。

如果 io.open 成功,它会返回一个文件对象(也称为文件句柄)。如果失败(例如在 "r" 模式下文件不存在,权限错误),它会返回 nil,后跟一个错误消息和错误代码。

local file, err = io.open("non_existent_file.txt", "r")
if not file then
print("Error opening file: ", err)
else
-- 处理文件
file:close()
end

完成文件操作后,务必使用 file:close() 关闭文件,以释放系统资源并确保数据被刷新到磁盘。

Lua 为标准输入 (io.stdin)、标准输出 (io.stdout) 和标准错误 (io.stderr) 提供了默认的文件句柄。像 io.read()、io.write() 和 print() 这样的函数默认使用这些句柄。

io.input(file_or_filename) 和 io.output(file_or_filename) 可以改变默认的输入/输出文件。

-- 如果 data.txt 不存在,则创建并写入一些内容
local initial_file_setup, err_setup = io.open("data.txt", "w")
if initial_file_setup then
initial_file_setup:write("Hello Lua Learner!\nThis is the second line.\nEnd of file.\n")
initial_file_setup:close()
else
print("Setup error: ", err_setup)
return -- 如果设置失败则退出
end
-- 使用隐式文件描述符的示例
local original_input = io.input() -- 保存原始默认输入
local original_output = io.output() -- 保存原始默认输出
-- 将默认输入设置为 data.txt
local file_in, err_in = io.input("data.txt")
if not file_in then
print("Error setting input: ", err_in)
else
local first_line = io.read("*l") -- 从默认输入(现在是 data.txt)读取一行
print("读取的第一行:", first_line)
io.close(file_in) -- 关闭与默认输入关联的文件
end
-- 恢复原始默认输入
io.input(original_input)
-- 使用默认输出追加到 data.txt
local file_out, err_out = io.output("data.txt", "a") -- io.output 使用模式 'a' 进行追加不符合标准用法,通常它会以写入模式打开
-- 最好使用显式文件句柄进行追加
-- 为了安全和清晰,这里我们使用显式文件句柄进行追加:
file_out, err_out = io.open("data.txt", "a")
if not file_out then
print("Error opening for append: ", err_out)
else
file_out:write("\nAppended via explicit handle.")
file_out:close()
end
-- 验证追加
local verify_file, err_verify = io.open("data.txt", "r")
if verify_file then
print("\n--- 追加后 data.txt 的内容 ---")
print(verify_file:read("*a"))
verify_file:close()
else
print("Error verifying: ", err_verify)
end
-- 注意:改变默认输入/输出不如使用显式文件句柄常见。

file:read(...) 方法(用于显式文件对象)或 io.read(...)(用于默认输入)用于读取数据。它接受一个或多个格式字符串:

格式描述
"*n" 或 "n"读取一个数字。在尝试读取之前跳过空格。如果在 EOF(文件结束)或无法解析为数字时返回 nil。
"*a" 或 "a"从当前位置读取整个文件。在 EOF 时返回空字符串。
"*l" 或 "l"读取下一行,丢弃换行符。在 EOF 时返回 nil。
"*L" 或 "L"读取下一行,保留换行符。在 EOF 时返回 nil。
数字读取最多 数字 个字节(一个包含该数量字符的字符串)。在 EOF 时返回 nil。
local file, err = io.open("data.txt", "r")
if file then
print("\n--- 逐行读取 data.txt ---")
local line1 = file:read("*l")
print("第 1 行:", line1)
local rest_of_file = file:read("*a")
print("文件剩余部分:", rest_of_file)
file:close()
else
print("Error reading file: ", err)
end

file:lines() 迭代器对于逐行读取文件非常方便:

print("\n--- 使用 file:lines() 读取 data.txt ---")
local file_lines, err_lines = io.open("data.txt", "r")
if file_lines then
for line in file_lines:lines() do
print("迭代器行:", line)
end
file_lines:close() -- 当传递文件对象时,lines() 迭代器不会自动关闭文件
else
print("Error opening for lines: ", err_lines)
end
-- io.lines(filename) 会自动打开、迭代和关闭文件
print("\n--- 使用 io.lines(filename) 读取 data.txt ---")
for line in io.lines("data.txt") do
print("io.lines 行:", line)
end

file:write(...) 方法(或用于默认输出的 io.write(...))将其参数写入文件。参数必须是字符串或数字(数字会被转换为字符串)。它不会自动添加换行符或分隔符。

local file, err = io.open("output.txt", "w") -- 以写入模式打开,如果文件存在则覆盖
if file then
file:write("Hello, Lua file output!\n")
file:write("This is line 2.", " And this is part of line 2.\n")
local num = 123
file:write("A number: ", num, "\n")
print("数据已写入 output.txt")
file:close()
else
print("Error writing to file: ", err)
end
  • file:seek(whence, offset): 设置文件位置。whence 可以是 "set"(从头开始)、"cur"(从当前位置开始)或 "end"(从末尾开始)。offset 是一个字节计数。file:seek() 返回当前位置。file:seek("cur", 0) 获取当前位置。
  • file:flush(): 将所有缓冲写入刷新到文件。
  • io.type(obj): 如果 obj 是一个已打开的文件句柄,则返回 "file";如果是已关闭的文件句柄,则返回 "closed file";否则返回 nil。
  • io.tmpfile(): 返回一个临时文件的句柄,以更新模式 ("w+b") 打开。该文件在程序退出或句柄关闭时会自动删除。

使用 seek 的示例:

local file, err = io.open("data.txt", "r")
if file then
file:seek("set", 6) -- 查找定位到第 7 个字节(索引从 0 开始)
local partial_read = file:read(5) -- 读取 5 个字节
print("\n查找定位后读取:'", partial_read, "'") -- 应该是 "Lua L"
local current_pos = file:seek("cur", 0)
print("当前位置:", current_pos)
file:seek("end", -12) -- 从末尾查找定位 12 个字节
local end_data = file:read("*a")
print("读取最后一部分:'", end_data, "'")
file:close()
else
print("Error in seek example: ", err)
end

错误处理: 务必检查 I/O 操作(特别是 io.open、file:read 和 file:write)的返回值,以优雅地处理潜在错误。

实际应用: 文件 I/O 对于读取配置文件、写入日志文件、处理数据集以及保存/加载应用程序状态至关重要。