03 文件系统(io)
作者:王棚嶙
一、概述
io库是LuatOS提供的文件操作核心库,该库提供了完整的文件读写、目录管理、文件系统操作等功能,支持二进制和文本模式的文件操作,能够处理各种存储设备上的文件系统,包括TF卡、SPI Flash等。io库与os库配合使用可以完成绝大多数文件系统操作需求。
1.1 存储系统概述
LuatOS的存储系统采用清晰的分层架构设计,其核心目标是为应用层提供统一、简洁的文件操作接口,从而屏蔽底层不同存储介质和文件系统的差异。开发者无需关心数据具体存储在何种硬件上,也无需关心底层文件系统的具体实现,只需调用统一的API即可完成文件操作。
该系统的层次结构与各组件间的协作关系如下图所示,其数据流与依赖关系自上而下贯穿各层:

1.1.1 物理硬件层
这是数据存储的物理载体,LuatOS支持多种常见的嵌入式存储硬件:
1、SPI/SDIO TF卡:通过高速SPI或SDIO接口连接的块存储设备,特点是容量大、可插拔,适用于存储大量数据(如音频、图片文件);
2、SPI NOR Flash:通过SPI接口访问的NOR型闪存。其特点是读写速度快,通常用于存放固件代码或需要快速读写的关键数据;
3、SPI NAND Flash:通过SPI接口访问的NAND型闪存。其特点是容量大、成本低,适用于存储日志等容量需求较大的数据;
1.1.2 文件系统实现层(fatfs&lf)
文件系统负责管理存储设备的空间,组织文件和目录结构。LuatOS根据硬件特性,适配了两种主流的嵌入式文件系统:
1、FATFS:一个兼容FAT12/FAT16/FAT32标准的通用文件系统。它通常与SD/TF卡配对使用,;
2、LFS(LittleFS):它通常与SPI NOR Flash和SPI NAND Flash配对使用,非常适合在裸Flash设备上运行;
1.1.3 统一API层 (io)
fs库不推荐新客户使用,该库仅对已经使用过的老客户进行保留,其功能已经完全移植到io库中,且更加完善,新客户建议都参考io核心库:
1、fs库:提供文件系统的额外操作不建议使用,请使用io库进行替代;
2、io库:提供标准的数据流操作接口,用于文件的打开、读写、关闭等,是处理文件内容的主要手段,包含fs库的所有功能,推荐使用;
1.1.4 应用层(luatOS应用脚本)
客户通过调用io库,fatfs库,lf库以及fs库中的api接口,来实现自己的业务逻辑。
1.1.5 推荐使用
| 你的需求 | 推荐使用的库和函数 | 理由 |
|---|---|---|
| 快速检查一个文件的大小 | io.exists(path) | 最简洁、最直接,一行代码搞定。 |
| 快速查看文件系统的整体使用情况(如剩余空间) | io.fsstat(path) | 提供通用信息,代码兼容性好。 |
| 需要对FAT格式的U盘、SD卡进行深度管理 | fatfs 库 (如fatfs.getfree(), fatfs.mount()) | 只有它才能提供FAT文件系统的专业控制和详细信息。 |
| 要读取、写入文件内容,或进行复杂的文件操作 | io 库 (如 io.open, file:read, file:write) | 这是进行文件I/O操作的标准和核心方式,不可替代。 |
对于FAT文件系统需求,使用 fatfs 库;
对于文件内容操作需求,使用 io 库;
1.2 文件句柄
文件句柄是操作系统或文件系统在成功打开一个文件后,返回给应用程序的一个抽象标识符。它不是一个数据容器,而是一个用于管理和操作该文件的接口;
示例
--[[
示例主要描述的就是如何通过 io.open() 函数获取文件句柄(file descriptor,简称 fd),以及如何利用这个文件句柄进行后续的各种文件操作;
这部分示例的核心内容可以概括为:
获取文件句柄:使用 io.open(filename, mode) 函数以指定的模式(如只读 "rb"、写入 "wb"、追加 "a" 等)打开一个文件。如果成功,该函数会返回一个非 nil 的文件句柄对象 fd;如果失败(例如文件不存在),则返回 nil。
使用文件句柄进行操作:
示例展示了在成功获取句柄 fd 后,可以调用其多种方法:
fd:read(...): 从文件中读取数据(按字节、按行或读取全部)。
fd:write(...): 向文件中写入数据。
fd:seek(...): 移动文件中的读写指针位置。
fd:close(): 非常重要的一步,在完成操作后必须关闭文件句柄,以释放系统资源。
因此,这部分的重点是教会用户如何创建和使用“文件句柄”这个核心对象来完成对文件的读写控制。
]]
-- io模块是lua原生模块,LuatOS增加了一些API
-- 请配合os模块一起使用
-- 只读模式, 打开文件
local fd = io.open("/xxx.txt", "rb")
-- 读写默认,打开文件
local fd = io.open("/xxx.txt", "wb")
-- 写入文件,且截断为0字节
local fd = io.open("/xxx.txt", "wb+")
-- 追加模式
local fd = io.open("/xxx.txt", "a")
-- 若文件打开成功, fd不为nil,否则就是失败了
-- 注意, 刷机时所添加的文件, 均在 /luadb 目录下, 只读
if fd then
-- 读取指定字节数,如果数据不足,就只返回实际长度的数据
local data = fd:read(12)
-- 按行读取
local line = fd:read("*l")
-- 全部读取
local line = fd:read("*a")
-- 数据写入, 仅w或a模式可调用
-- 数据需要是字符串, lua的字符串是带长度的,可以包含任何二进制数据
fd:write("xxxx")
-- 以下是写入0x12, 0x13
fd:write(string.char(0x12, 0x13))
-- 移动句柄,绝对坐标
fd:seek(1024, io.SEEK_SET)
-- 移动句柄,相对坐标
fd:seek(1024, io.SEEK_CUR)
-- 移动句柄,反向绝对坐标,从文件结尾往文件头部算
fd:seek(124, io.SEEK_END)
-- 执行完操作后,一定要关掉文件
fd:close()
end
1.3 关于"块"(block )的概念解释
块(Block)是文件系统管理存储的基本单位,但需要注意:
1.3.1 块的数量是整数
io.fsstat 返回的"总空间"和"已用空间"值表示的是块的数量这些都是整数。
1.3.2 块的大小也是整数
每个块的大小是固定的,通常是512字节或4096字节等2的幂次方。
1.3.3 为什么需要单独返回块大小
1、不同的文件系统可能使用不同的块大小
2、同一设备上的不同分区可能使用不同的块大小
3、知道块大小才能将块数量转换为实际字节数
1.3.4 计算实际空间
1、总空间(字节) = 总块数 × 块大小
2、已用空间(字节) = 已用块数 × 块大小
3、空闲空间(字节) = (总块数 - 已用块数) × 块大小
1.3.5 为什么会有这样的设计?
这种设计(返回块数和块大小而不是直接返回字节数)有以下几个原因:
1、效率:文件系统内部是以块为单位管理空间的,直接返回块数更高效
2、精确性:避免浮点数运算可能带来的精度问题
3、灵活性:适应不同块大小的文件系统
1.4 读写模式的选择
在LuatOS文件操作中,选择合适的文件打开模式至关重要,这直接影响文件操作的安全性、效率和功能。以下是各种模式的适用场景和推荐用法:
1.4.1 模式选择指南
| 需求场景 | 推荐模式 | 理由说明 |
|---|---|---|
| 只读配置文件 | 'r' 或 'rb' | 文件必须存在,只读最安全,避免意外修改 |
| 创建新文件 | 'w' 或 'wb' | 文件不存在则创建,存在则清空,适合全新写入 |
| 追加日志 | 'a' 或 'ab' | 所有写入自动追加到末尾,不会破坏现有内容 |
| 修改现有文件 | 'r+' 或 'r+b' | 文件必须存在,可读写,文件指针位于开头 |
| 创建并读写 | 'w+' 或 'w+b' | 文件不存在则创建,存在则清空,可读写 |
| 读取并追加 | 'a+' 或 'a+b' | 文件不存在则创建,可读取,写入追加到末尾 |
1.4.2 r+模式使用建议
'r+'模式是一个强大的读写模式,但需要谨慎使用:
适用场景:
- 需要修改已存在文件的中部内容
- 需要随机读写文件数据
- 文件已确认存在,需要更新部分内容
不适用场景:
- 不能用'w'模式修改中间内容:'w'模式会清空整个文件,只能重新写入完整内容
- 不能用'a'模式修改中间内容:'a'模式只能追加到文件末尾
注意事项:
- 文件必须存在,否则会返回
nil - 文件指针位于开头,覆盖写入会影响原有内容
- 建议配合
io.exists()进行预检查
1.4.3 二进制模式说明
LuatOS默认就是二进制模式,指定'b'主要是为了代码可读性和与其他平台兼容性:
'rb'、'wb'、'ab':明确指定二进制模式'r+b'、'w+b'、'a+b':二进制读写模式- 所有组合顺序都支持(如
'rb+'和'r+b'等效)
二、准备硬件环境
参考:硬件环境清单第二章节内容,准备以及组装好硬件环境。
2.1 780EHM
1、Air780EHM核心板一块
2、TYPE-C USB数据线一根
3、SIM卡一张
4、Air780EHM核心板和数据线的硬件接线方式为
- Air780EHM核心板通过TYPE-C USB口供电;(核心板USB旁边的开关拨到on一端)
- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
2.2 780EPM
1、Air780EPM核心板一块
2、TYPE-C USB数据线一根
3、SIM卡一张
4、Air780EPM核心板和数据线的硬件接线方式为
- Air780EPM核心板通过TYPE-C USB口供电;(核心板USB旁边的开关拨到on一端)
- TYPE-C USB数据线直接插到核心板的TYPE-C USB座子,另外一端连接电脑USB口;
三、软件环境
在开始实践本示例之前,先筹备一下软件环境:
3.1 780EHM
1、Luatools下载调试工具:下载调试工具
2、本demo开发测试时使用的固件为Air780EHM V2028 版本固件,本demo对固件版本没有什么特殊要求,所以你如果要测试本demo时,可以直接使用最新版本的内核固件Air780EPM固件,Air780EHM固件;如果发现最新版本的内核固件测试有问题,可以使用我们开发本demo时使用的内核固件版本来对比测试。
3、LuatOS 需要的脚本和资源文:脚本资源下载
4、lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件
5、准备好软件环境之后,接下来查看如何烧录项目文件到 Air780EHM 核心板使用说明 ,将本篇文章中演示使用的项目文件烧录到 Air780EHM 核心板中
3.2 780EPM
1、Luatools下载调试工具:下载调试工具
2、本demo开发测试时使用的固件为Air780EPM V2028 版本固件,本demo对固件版本没有什么特殊要求,所以你如果要测试本demo时,可以直接使用最新版本的内核固件Air780EPM固件,Air780EHM固件;如果发现最新版本的内核固件测试有问题,可以使用我们开发本demo时使用的内核固件版本来对比测试。
3、LuatOS 需要的脚本和资源文:脚本资源下载
4、lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件
5、准备好软件环境之后,接下来查看如何烧录项目文件到Air780EPM开发板,将本篇文章中演示使用的项目文件烧录到Air780EPM开发板中。
四、文件系统(io)基本用法
4.1 本教程实现的功能定义:
- 此次 demo 验证文件创建、文件追加、文件删除、文件重命名、文件拷贝、文件移动、文件大小、文件枚举的函数接口的介绍和使用。
4.2 文章内容引用
- 以上接口函数不做详细介绍,可通过此链接查看具体介绍:io - io 操作(扩展),os操作
4.3 文件创建
文件创建是指新建一个空文件或向一个不存在的文件路径写入内容,从而生成该文件。
使用例子:
-- 方法1:使用io.open创建空文件(如果文件已存在,则覆盖)
local fd = io.open("/newfile.txt", "w")
if fd then
-- 文件已成功创建(或覆盖),此时文件为空
fd:close()
log.info("文件创建成功(空文件)")
else
log.error("文件创建失败")
end
-- 方法2:通过写入内容创建文件
local content = "这是文件的内容"
local fd = io.open("/newfile_with_content.txt", "w")
if fd then
fd:write(content)
fd:close()
log.info("文件创建成功并写入内容")
else
log.error("文件创建失败")
end
注:
- 使用
io.open函数并传入文件路径和模式(如"w"表示写模式)来创建文件。如果文件已存在,写模式会覆盖原有内容。 - 创建文件后,务必使用
:close方法关闭文件,以确保数据正确写入并释放资源。 - 可以通过写入内容来同时创建文件并填充数据。
- 常见问题:
- 文件创建失败:可能由于路径不存在、权限不足或磁盘空间不足等原因。
- 文件内容未写入:可能是因为文件未正确关闭或写入过程中发生错误。
4.4 文件追加
文件追加是指在文件的末尾添加内容,而不覆盖原有内容。如果文件不存在,追加模式会自动创建文件。
使用例子:
log.info("-------------------文件追加---------------")
-- 打开文件以追加模式
local fd = io.open("/data/newfile_with_content.txt", "rb")
if fd then
local data_old = fd:read("*a")
log.info("文件创建初始内容:",data_old)
-- 关闭文件
fd:close()
local fd1 = io.open("/data/newfile_with_content.txt", "a")
-- 写入内容
fd1:write("我是追加的内容\n")
-- 关闭文件
fd1:close()
local fd2 = io.open("/data/newfile_with_content.txt", "rb")
local data_new = fd2:read("*a")
log.info("文件追加之后的内容:",data_new)
-- 关闭文件
fd2:close()
end
注:使用 io.open 函数以 "a" 模式打开文件,然后使用 :write 方法写入内容,最后使用 :close 方法关闭文件。
4.5 文件删除
文件删除是指从文件系统中移除指定的文件。无法删除只读文件或不存在的文件。
使用例子:
-- 删除文件
local success, err = os.remove("/destination/source.txt")
if success then
log.info("文件删除成功")
else
log.error("文件删除失败:" .. err)
end
注 :使用 os.remove 函数并传入文件路径来删除文件。函数返回两个值:成功时为 true,失败时为 nil 和错误原因字符串。
4.6 文件重命名
文件重命名是指将文件从一个名称更改为另一个名称,无法重命名只读文件或跨文件系统重命名文件,可以确保新文件路径所在的目录存在,以避免重命名失败。
使用例子:
("----------------命名文件---------------")
-- 重命名文件
local success, err = os.rename("/data/newfile_with_content.txt", "/data/newname.txt")
if success then
log.info("文件重命名成功")
else
log.error("文件重命名失败:" .. err)
end
注:使用 os.rename 函数并传入旧文件路径和新文件路径来重命名文件。函数返回两个值:成功时为 true,失败时为 nil 和错误原因字符串。
4.7 文件拷贝
文件拷贝是指将文件的内容从一个位置复制到另一个位置。
使用例子(注意:LuatOS 标准 API 未直接提供文件拷贝函数,但可以通过读取源文件并写入目标文件来实现):
log.info("----------------文件拷贝---------------")
---文件拷贝
-- 读取源文件内容
local fd_src = io.open("/data/newname.txt", "rb")
if fd_src then
local content = fd_src:read("*a")
fd_src:close()
-- 写入目标文件
local fd_dest = io.open("/data/destination.txt", "wb")
if fd_dest then
fd_dest:write(content)
fd_dest:close()
log.info ("文件拷贝成功")
else
log.error("无法打开目标文件")
end
else
log.error("无法打开源文件")
end
注:通过读取源文件内容并将其写入目标文件来实现文件拷贝。
4.8 文件移动
文件移动是指将文件从一个位置移动到另一个位置,这通常通过重命名文件(如果目标位置在同一文件夹)或先拷贝再删除源文件来实现。
使用例子(注意:LuatOS 标准 API 未直接提供文件移动函数,但可以通过重命名或拷贝 + 删除来实现):
log.info("----------------移动文件---------------")
local ret, errio = io.mkdir("/destination/")
if ret then
log.info ("文件夹创建成功")
else
log.error("文件夹创建失败")
end
-- 移动文件:重命名(适用于同一文件系统)
local success, err = os.rename("/data/newname.txt", "/destination/source.txt")
if success then
log.info ("文件移动成功(重命名)")
else
log.error("文件移动失败(重命名):" .. err)
end
注:根据目标位置与源文件位置的关系,选择重命名或拷贝 + 删除的方法来实现文件移动。
4.9 文件大小
获取文件的大小(以字节为单位)。
使用例子:
-- 获取文件大小
local size = io.fileSize("/data/newname.txt")
if size then
log.info("文件大小:" .. size .. " 字节")
else
log.error("无法获取文件大小")
end
注:使用 io.fileSize 函数并传入文件路径来获取文件大小。函数返回一个整数表示文件大小(以字节为单位),如果失败则返回 nil。
4.10 文件枚举
列出指定目录下的所有文件。
使用例子:
local ret, data = io.lsdir("/data/",10,0)
if ret then
log.info("fs", "lsdir", json.encode(data))
else
log.info("fs", "lsdir", "fail", ret, data)
end
注:使用 io.lsdir 函数并传入目录路径来列出该目录下的所有文件。函数返回两个值:成功时为 true 和一个包含文件名的数组,失败时为 nil 和错误信息。
五、文件系统整体演示
5.1功能验证
5.1.1运行结果展示
(1)文件操作演示
[2025-10-22 15:23:25.096][000000000.595] I/user.文件系统操作 ===== 开始文件系统操作 =====
[2025-10-22 15:23:25.104][000000000.601] I/user. io.fsstat成功: 总空间=192块 已用=20块 块大小=4096字节 类型=lfs
[2025-10-22 15:23:25.118][000000000.644] I/user.io.mkdir 目录创建成功 路径:/flash_demo
[2025-10-22 15:23:25.127][000000000.648] I/user.文件创建 文件写入成功 路径:/flash_demo/boottime
[2025-10-22 15:23:25.145][000000000.651] I/user.io.exists 文件存在 路径:/flash_demo/boottime
[2025-10-22 15:23:25.157][000000000.654] I/user.io.fileSize 文件大小:59字节 路径:/flash_demo/boottime
[2025-10-22 15:23:25.165][000000000.657] I/user.文件读取 路径:/flash_demo/boottime 内容:这是内置Flash文件系统API文档示例的测试内容
[2025-10-22 15:23:25.181][000000000.660] I/user.启动计数 文件内容: 这是内置Flash文件系统API文档示例的测试内容 十六进制: E8BF99E698AFE58685E7BDAE466C617368E69687E4BBB6E7B3BBE7BB9F415049E69687E6A1A3E7A4BAE4BE8BE79A84E6B58BE8AF95E58685E5AEB9 118
[2025-10-22 15:23:25.189][000000000.660] I/user.启动计数 当前值: 0
[2025-10-22 15:23:25.211][000000000.660] I/user.启动计数 更新值: 1
[2025-10-22 15:23:25.217][000000000.663] I/user.文件写入 路径:/flash_demo/boottime 内容: 1
[2025-10-22 15:23:25.224][000000000.669] I/user.文件创建 路径:/flash_demo/test_a 初始内容:ABC
[2025-10-22 15:23:25.245][000000000.672] I/user.文件追加 路径:/flash_demo/test_a 追加内容:def
[2025-10-22 15:23:25.251][000000000.675] I/user.文件验证 路径:/flash_demo/test_a 内容:ABCdef 结果: 成功
[2025-10-22 15:23:25.267][000000000.678] I/user.文件创建 路径:/flash_demo/testline 写入3行文本
[2025-10-22 15:23:25.277][000000000.681] I/user.按行读取 路径:/flash_demo/testline 第1行: abc
[2025-10-22 15:23:25.285][000000000.682] I/user.按行读取 路径:/flash_demo/testline 第2行: 123
[2025-10-22 15:23:25.295][000000000.682] I/user.按行读取 路径:/flash_demo/testline 第3行: wendal
[2025-10-22 15:23:25.301][000000000.689] I/user.os.rename 文件重命名成功 原路径:/flash_demo/test_a 新路径:/flash_demo/renamed_file.txt
[2025-10-22 15:23:25.312][000000000.694] D/vfs fopen /flash_demo/test_a r not found
[2025-10-22 15:23:25.321][000000000.694] I/user.验证结果 重命名验证成功 新文件存在 原文件不存在
[2025-10-22 15:23:25.340][000000000.695] I/user.目录操作 ===== 开始目录列举 =====
[2025-10-22 15:23:25.348][000000000.706] I/user.fs lsdir [{"name":"boottime","size":1,"type":0},{"name":"renamed_file.txt","size":6,"type":0},{"name":"testline","size":15,"type":0}]
[2025-10-22 15:23:25.359][000000000.710] I/user.os.remove 文件删除成功 路径:/flash_demo/renamed_file.txt
[2025-10-22 15:23:25.366][000000000.713] D/vfs fopen /flash_demo/renamed_file.txt r not found
[2025-10-22 15:23:25.373][000000000.713] I/user.验证结果 renamed_file.txt文件删除验证成功
[2025-10-22 15:23:25.378][000000000.716] I/user.os.remove testline文件删除成功 路径:/flash_demo/testline
[2025-10-22 15:23:25.390][000000000.719] D/vfs fopen /flash_demo/testline r not found
[2025-10-22 15:23:25.395][000000000.719] I/user.验证结果 testline文件删除验证成功
[2025-10-22 15:23:25.415][000000000.723] I/user.os.remove 文件删除成功 路径:/flash_demo/boottime
[2025-10-22 15:23:25.423][000000000.726] D/vfs fopen /flash_demo/boottime r not found
[2025-10-22 15:23:25.444][000000000.726] I/user.验证结果 boottime文件删除验证成功
[2025-10-22 15:23:25.453][000000000.732] I/user.io.rmdir 目录删除成功 路径:/flash_demo
[2025-10-22 15:23:25.464][000000000.734] I/user.验证结果 目录删除验证成功
[2025-10-22 15:23:25.477][000000000.740] I/user. io.fsstat 操作后文件系统信息: 总空间=192块 已用=20块 块大小=4096字节 类型=lfs
[2025-10-22 15:23:25.484][000000000.740] I/user.文件系统操作 ===== 文件系统操作完成 =====
5.1.2完整实例
function flash_fs_io_task()
-- 使用内置Flash文件系统,根目录为"/",
local base_path = "/"
-- 创建一个目录
local demo_dir = "flash_demo"
-- 文件名
local dir_path = base_path .. demo_dir
-- ########## 开始进行内置Flash文件系统操作 ##########
log.info("文件系统操作", "===== 开始文件系统操作 =====")
-- 1. 获取文件系统信息 (使用 io.fsstat接口)
local success, total_blocks, used_blocks, block_size, fs_type = io.fsstat(base_path)
if success then
log.info(" io.fsstat成功:",
"总空间=" .. total_blocks .. "块",
"已用=" .. used_blocks .. "块",
"块大小=" .. block_size.."字节",
"类型=" .. fs_type)
else
log.error(" io.fsstat", "获取文件系统信息失败")
return
end
-- 2. 创建目录
-- 如果目录不存在,则创建目录
if not io.dexist(dir_path) then
-- 创建目录
if io.mkdir(dir_path) then
log.info("io.mkdir", "目录创建成功", "路径:" .. dir_path)
else
log.error("io.mkdir", "目录创建失败", "路径:" .. dir_path)
return
end
else
log.warn("io.mkdir", "目录已存在,跳过创建", "路径:" .. dir_path)
end
-- 3. 创建并写入文件
local file_path = dir_path .. "/boottime"
local file = io.open(file_path, "wb")
if file then
file:write("这是内置Flash文件系统API文档示例的测试内容")
file:close()
log.info("文件创建", "文件写入成功", "路径:" .. file_path)
else
log.error("文件创建", "文件创建失败", "路径:" .. file_path)
return
end
-- 4. 检查文件是否存在
if io.exists(file_path) then
log.info("io.exists", "文件存在", "路径:" .. file_path)
else
log.error("io.exists", "文件不存在", "路径:" .. file_path)
return
end
-- 5. 获取文件大小 (使用io.fileSize接口)
local file_size = io.fileSize(file_path)
if file_size then
log.info("io.fileSize", "文件大小:" .. file_size .. "字节", "路径:" .. file_path)
else
log.error("io.fileSize", "获取文件大小失败", "路径:" .. file_path)
return
end
-- 6. 读取文件内容
file = io.open(file_path, "rb")
if file then
local content = file:read("*a")
log.info("文件读取", "路径:" .. file_path, "内容:" .. content)
file:close()
else
log.error("文件操作", "无法打开文件读取内容", "路径:" .. file_path)
return
end
-- 7. 启动计数文件操作
local count = 0
file = io.open(file_path, "rb")
if file then
local data = file:read("*a")
log.info("启动计数", "文件内容:", data, "十六进制:", data:toHex())
count = tonumber(data) or 0
file:close()
else
log.warn("启动计数", "文件不存在或无法打开")
end
log.info("启动计数", "当前值:", count)
count = count + 1
log.info("启动计数", "更新值:", count)
file = io.open(file_path, "wb")
if file then
file:write(tostring(count))
file:close()
log.info("文件写入", "路径:" .. file_path, "内容:", count)
else
log.error("文件写入", "无法打开文件", "路径:" .. file_path)
return
end
-- 8. 文件追加测试
local append_file = dir_path .. "/test_a"
os.remove(append_file) -- 清理旧文件
file = io.open(append_file, "wb")
if file then
file:write("ABC")
file:close()
log.info("文件创建", "路径:" .. append_file, "初始内容:ABC")
else
log.error("文件创建", "无法创建文件", "路径:" .. append_file)
return
end
file = io.open(append_file, "a+")
if file then
file:write("def")
file:close()
log.info("文件追加", "路径:" .. append_file, "追加内容:def")
else
log.error("文件追加", "无法打开文件进行追加", "路径:" .. append_file)
return
end
-- 验证追加结果
file = io.open(append_file, "r")
if file then
local data = file:read("*a")
log.info("文件验证", "路径:" .. append_file, "内容:" .. data, "结果:",
data == "ABCdef" and "成功" or "失败")
file:close()
else
log.error("文件验证", "无法打开文件进行验证", "路径:" .. append_file)
return
end
-- 9. 按行读取测试
local line_file = dir_path .. "/testline"
file = io.open(line_file, "w")
if file then
file:write("abc\n")
file:write("123\n")
file:write("wendal\n")
file:close()
log.info("文件创建", "路径:" .. line_file, "写入3行文本")
else
log.error("文件创建", "无法创建文件", "路径:" .. line_file)
return
end
file = io.open(line_file, "r")
if file then
log.info("按行读取", "路径:" .. line_file, "第1行:", file:read("*l"))
log.info("按行读取", "路径:" .. line_file, "第2行:", file:read("*l"))
log.info("按行读取", "路径:" .. line_file, "第3行:", file:read("*l"))
file:close()
else
log.error("按行读取", "无法打开文件", "路径:" .. line_file)
return
end
-- 10. 文件重命名
local old_path = append_file
local new_path = dir_path .. "/renamed_file.txt"
local success, err = os.rename(old_path, new_path)
if success then
log.info("os.rename", "文件重命名成功", "原路径:" .. old_path, "新路径:" .. new_path)
-- 验证重命名结果
if io.exists(new_path) and not io.exists(old_path) then
log.info("验证结果", "重命名验证成功", "新文件存在", "原文件不存在")
else
log.error("验证结果", "重命名验证失败")
end
else
log.error("os.rename", "重命名失败", "错误:" .. tostring(err), "原路径:" .. old_path)
return
end
-- 11. 列举目录内容
log.info("目录操作", "===== 开始目录列举 =====")
local ret, data = io.lsdir(dir_path, 50, 0)
if ret then
log.info("fs", "lsdir", json.encode(data))
else
log.info("fs", "lsdir", "fail", ret, data)
return
end
-- 12. 删除文件测试
if os.remove(new_path) then
log.info("os.remove", "文件删除成功", "路径:" .. new_path)
if not io.exists(new_path) then
log.info("验证结果", "renamed_file.txt文件删除验证成功")
else
log.error("验证结果", "renamed_file.txt文件删除验证失败")
end
else
log.error("os.remove", "renamed_file.txt文件删除失败", "路径:" .. new_path)
return
end
if os.remove(line_file) then
log.info("os.remove", "testline文件删除成功", "路径:" .. line_file)
if not io.exists(line_file) then
log.info("验证结果", "testline文件删除验证成功")
else
log.error("验证结果", "testline文件删除验证失败")
end
else
log.error("os.remove", "testline文件删除失败", "路径:" .. line_file)
return
end
if os.remove(file_path) then
log.info("os.remove", "文件删除成功", "路径:" .. file_path)
if not io.exists(file_path) then
log.info("验证结果", "boottime文件删除验证成功")
else
log.error("验证结果", "boottime文件删除验证失败")
end
else
log.error("os.remove", "boottime文件删除失败", "路径:" .. file_path)
return
end
-- 13. 删除目录
if io.rmdir(dir_path) then
log.info("io.rmdir", "目录删除成功", "路径:" .. dir_path)
if not io.dexist(dir_path) then
log.info("验证结果", "目录删除验证成功")
else
log.error("验证结果", "目录删除验证失败")
end
else
log.error("io.rmdir", "目录删除失败", "路径:" .. dir_path)
return
end
-- 再次获取文件系统信息,查看空间变化
local final_success, final_total_blocks, final_used_blocks, final_block_size, final_fs_type = io.fsstat(base_path)
if final_success then
log.info(" io.fsstat", "操作后文件系统信息:",
"总空间=" .. final_total_blocks .. "块",
"已用=" .. final_used_blocks .. "块",
"块大小=" .. final_block_size.."字节",
"类型=" .. final_fs_type)
end
log.info("文件系统操作", "===== 文件系统操作完成 =====")
end
sys.taskInit(flash_fs_io_task)
六、总结
-
在 Lua 编程语言中,文件系统操作主要通过 io 库来实现。io 库提供了一系列函数,用于文件的打开、读取、写入、关闭以及目录的遍历等操作。
-
首先,io 库提供了两种文件处理接口风格:隐式文件句柄和显式文件句柄。隐式文件句柄通过 io 表提供的函数进行操作,而显式文件句柄则通过 io.open 函数返回的文件句柄对象的方法进行操作。
-
在文件打开方面,io.open 函数是关键,它接受文件名和模式作为参数,并返回一个文件句柄对象。文件模式包括只读("r")、只写("w")、追加("a")等,还可以加上"b"来表示二进制模式。如果文件打开失败,io.open 函数会返回 nil 和错误信息。
-
文件读取操作主要通过文件句柄对象的 read 方法来实现。read 方法可以接受不同的参数来读取文件的不同部分,如读取下一行、读取整个文件等。
-
文件写入操作则通过文件句柄对象的 write 方法来实现。write 方法将字符串或数字写入文件。如果文件以只读模式打开,则无法进行写入操作。
-
完成文件操作后,应使用文件句柄对象的 close 方法关闭文件,以释放系统资源。
-
此外,io 库还提供了一些其他有用的函数,如 io.input 和 io.output,它们可以设置默认的输入输出文件,使得后续的输入输出操作都针对这些默认文件。io.lines 函数则返回一个迭代器,用于从文件中逐行读取内容。
-
需要注意的是,虽然 io 库提供了强大的文件系统操作功能,但在进行文件操作时仍需谨慎,以避免文件损坏或数据丢失等风险。同时,对于跨文件系统的文件移动等操作,可能需要结合其他函数或方法来实现。
七、常见问题:
7.1 文件操作失败时如何定位问题?
- 检查文件路径是否正确。
- 确认文件是否存在且可访问。
- 检查磁盘空间是否充足。
- 查看错误日志以获取更多信息。
7.2 如何提高文件操作的效率?
- 对于大文件操作,可以考虑使用流式处理或分块处理来减少内存占用。
- 使用合适的文件打开模式(如只读、只写等)以减少不必要的操作。
八、扩展
- 文件加密与解密:可以结合加密库对文件进行加密存储和解密读取,以提高数据的安全性。
- 文件备份与恢复:可以定期将文件备份到外部存储设备或云存储中,并在需要时恢复文件。
- 文件系统监控:可以监控文件系统的变化(如文件创建、删除、修改等),以便及时响应和处理。