io - 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'
等效)
二、核心示例
1、核心示例是指:使用本库文件提供的核心 API,开发的基础业务逻辑的演示代码;
2、核心示例的作用是:帮助开发者快速理解如何使用本库,所以核心示例的逻辑都比较简单;
3、更加完整和详细的 demo,请参考 LuatOS 仓库 中各个产品目录下的 demo/fs_io或者demo/tf_card;
-- 列出所有挂载点,如不需要,可注释掉。
data = io.lsmount()
log.info("fs", "lsmount", json.encode(data))
-- ########## 功能: 启用fatfs调试模式 ##########
-- fatfs.debug(1) -- 若挂载失败,可以尝试打开调试信息,查找原因.(设置调试模式)
-- 执行tfcard文件操作演示
log.info("文件操作", "===== 开始文件操作 =====")
dir_path = "/sd/io_test"
-- 1. 创建目录
if io.mkdir(dir_path) then
log.info("io.mkdir", "目录创建成功", "路径:" .. dir_path)
else
-- 检查是否目录已存在
if io.exists(dir_path) then
log.warn("io.mkdir", "目录已存在,跳过创建", "路径:" .. dir_path)
else
log.error("io.mkdir", "目录创建失败且目录不存在", "路径:" .. dir_path)
goto resource_cleanup
end
end
-- 2. 创建并写入文件
file_path = dir_path .. "/boottime"
file = io.open(file_path, "wb")
if file then
file:write("这是io库API文档示例的测试内容")
file:close()
--在LuatOS文件操作中,执行file:close()是必须且关键的操作,它用于关闭文件句柄,释放资源,并确保数据被正确写入磁盘。
-- 如果不执行file:close(),可能会导致数据丢失、文件损坏或其他不可预测的问题。
log.info("文件创建", "文件写入成功", "路径:" .. file_path)
else
log.error("文件创建", "文件创建失败", "路径:" .. file_path)
goto resource_cleanup
end
-- 3. 检查文件是否存在
if io.exists(file_path) then
log.info("io.exists", "文件存在", "路径:" .. file_path)
else
log.error("io.exists", "文件不存在", "路径:" .. file_path)
goto resource_cleanup
end
-- 4. 获取文件大小
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)
goto resource_cleanup
end
-- 5. 读取文件内容
file = io.open(file_path, "rb")
if file then
content = file:read("*a")
log.info("文件读取", "路径:" .. file_path, "内容:" .. content)
file:close()
else
log.error("文件操作", "无法打开文件读取内容", "路径:" .. file_path)
goto resource_cleanup
end
-- 6. 启动计数文件操作
count = 0
--以只读模式打开文件
file = io.open(file_path, "rb")
if file then
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)
goto resource_cleanup
end
-- 7. 文件追加测试
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)
goto resource_cleanup
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)
goto resource_cleanup
end
-- 验证追加结果
file = io.open(append_file, "r")
if file then
data = file:read("*a")
log.info("文件验证", "路径:" .. append_file, "内容:" .. data, "结果:",
data == "ABCdef" and "成功" or "失败")
file:close()
else
log.error("文件验证", "无法打开文件进行验证", "路径:" .. append_file)
goto resource_cleanup
end
-- 8. 按行读取测试
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)
goto resource_cleanup
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)
goto resource_cleanup
end
-- 9. 文件重命名
old_path = append_file
new_path = dir_path .. "/renamed_file.txt"
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)
goto resource_cleanup
end
-- 10. 列举目录内容
log.info("目录操作", "===== 开始目录列举 =====")
ret, data = io.lsdir(dir_path, 50, 0) -- 50表示最多返回50个文件,0表示从目录开头开始
if ret then
log.info("fs", "lsdir", json.encode(data))
else
log.info("fs", "lsdir", "fail", ret, data)
goto resource_cleanup
end
-- 11. 删除文件测试
-- 测试删除renamed_file.txt文件
if os.remove(new_path) then
log.info("os.remove", "文件删除成功", "路径:" .. new_path)
-- 验证renamed_file.txt删除结果
if not io.exists(new_path) then
log.info("验证结果", "renamed_file.txt文件删除验证成功")
else
log.error("验证结果", "renamed_file.txt文件删除验证失败")
end
else
log.error("io.remove", "renamed_file.txt文件删除失败", "路径:" .. new_path)
goto resource_cleanup
end
-- 测试删除testline文件
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("io.remove", "testline文件删除失败", "路径:" .. line_file)
goto resource_cleanup
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("io.remove", "boottime文件删除失败", "路径:" .. file_path)
goto resource_cleanup
end
-- 12. 删除目录(不能删除非空目录,所以在删除目录前要确保目录内没有文件或子目录)
if io.rmdir(dir_path) then
log.info("io.rmdir", "目录删除成功", "路径:" .. dir_path)
-- 验证删除结果
if not io.exists(dir_path) then
log.info("验证结果", "目录删除验证成功")
else
log.error("验证结果", "目录删除验证失败")
end
else
log.error("io.rmdir", "目录删除失败", "路径:" .. dir_path)
goto resource_cleanup
end
log.info("文件操作", "===== 文件操作完成 =====")
三、常量详解
核心库常量,顾名思义是由合宙 LuatOS 内核固件中定义的、不可重新赋值或修改的固定值,在脚本代码中不需要声明,可直接调用;
每个常量对应的常量取值仅做日志打印时查询使用,不要将这个常量取值用做具体的业务逻辑判断,因为LuatOS内核固件可能会变更每个常量对应的常量取值;
如果用做具体的业务逻辑判断,一旦常量取值发生改变,业务逻辑就会出错;
3.1 io.SEEK_SET
常量含义:从文件开头为基点;
数据类型:number;
常量取值:0;
示例代码:-- 移动到1024字节处
local pos = fd:seek(1024, io.SEEK_SET)
3.2 io.SEEK_CUR
常量含义:以文件当前指针位置为基点;
数据类型:number;
常量取值:1;
示例代码:-- 从当前位置向后移动1024字节
local pos = fd:seek(1024, io.SEEK_CUR)
3.3 io.SEEK_END
常量含义:以文件末尾为基点;
数据类型:number;
常量取值:2;
示例代码:-- 从文件末尾向前移动124字节
local pos = fd:seek(-124, io.SEEK_END)
四、函数详解
4.1 io.exists(path)
功能
判断文件是否存在;
注意事项
1、可用于检查文件是否存在;
2、不区分文件类型;
参数
path
参数含义:文件路径;
数据类型:string;
取值范围:有效文件路径;
是否必选:是;
注意事项:暂无;
参数示例:"/boottime";
返回值 local exists = io.exists(path)
有一个返回值 exists
exists
含义说明:文件存在状态;
数据类型:boolean;
取值范围:true或false;
注意事项:true-存在, false-不存在;
返回示例:true;
示例
dir_path = "/sd/io_test"
-- 1. 创建目录
if io.mkdir(dir_path) then
log.info("io.mkdir", "目录创建成功", "路径:" .. dir_path)
else
-- 检查是否目录已存在
if io.exists(dir_path) then
log.warn("io.mkdir", "目录已存在,跳过创建", "路径:" .. dir_path)
else
log.error("io.mkdir", "目录创建失败且目录不存在", "路径:" .. dir_path)
end
end
-- 2. 创建并写入文件
file_path = dir_path .. "/boottime"
file = io.open(file_path, "wb")
if file then
file:write("这是io库API文档示例的测试内容")
file:close()
log.info("文件创建", "文件写入成功", "路径:" .. file_path)
else
log.error("文件创建", "文件创建失败", "路径:" .. file_path)
end
if io.exists(file_path) then
log.info("io.exists", "文件存在", "路径:" .. file_path)
else
log.error("io.exists", "文件不存在", "路径:" .. file_path)
end
4.2 io.fileSize(path)
功能
获取文件大小;
注意事项
1、文件不存在时返回nil;
2、必须提供有效的文件路径;
参数
path
参数含义:文件系统路径;
数据类型:string;
取值范围:有效文件系统路径;
是否必选:是;
注意事项:暂无;
参数示例:"/boottime";
返回值
local size = io.fileSize(path)
有一个返回值 size
size
含义说明:文件字节数;
数据类型:number/nil;
取值范围:≥0的整数或nil;
注意事项:成功返回字节数,失败返回nil;
返回示例:41
示例
dir_path = "/sd/io_test"
file = io.open(dir_path, "wb")
if file then
file:write("这是io库API文档示例的测试内容")
file:close()
log.info("文件创建", "文件写入成功", "路径:" .. file_path)
else
log.error("文件创建", "文件创建失败", "路径:" .. file_path)
end
file_size = io.fileSize(dir_path)
-- io.fileSize 文件大小:41字节 路径:/sd/io_test
log.info("io.fileSize", "文件大小:" .. file_size .. "字节", "路径:" .. dir_path)
4.3 io.readFile(path, mode, offset, len)
功能
读取整个文件内容;
注意事项
1、注意大文件可能消耗大量内存;
2、支持指定读取范围和模式;
3、如果文件较大,占用的内存会很大,在项目内存紧张的情况下,读取一个大文件,不要一次性全部读取出来,要分段读取操作;
参数
path
参数含义:文件系统路径;
数据类型:string;
取值范围:有效文件系统路径;
是否必选:是;
注意事项:暂无;
参数示例:"/abc.txt";
mode
参数含义:文件访问模式;
数据类型:string;
取值范围:"rb"等,详情参考下面的模式对照表;
是否必选:否;
注意事项:默认"rb";
参数示例:"rb";
offset
参数含义:读取起始偏移;
数据类型:number;
取值范围:≥0整数;
是否必选:否;
注意事项:默认0;
参数示例:128;
len
参数含义:读取字节数;
数据类型:number;
取值范围:≥0整数;
是否必选:否;
注意事项:默认整个文件;
参数示例:512;
返回值
local data = io.readFile(path, mode, offset, len)
有一个返回值 data
data
含义说明:文件内容;
数据类型:string/nil;
取值范围:字符串或nil;
注意事项:1、成功返回数据;
2、路径错误,失败返回nil;
3、当请求的读取范围部分超出文件末尾时,函数不会报错或失败,而是返回从偏移量开始到文件末尾的所有可用数据;
4、当指定的 offset 已经大于或等于文件的大小,返回空字符串;
返回示例:"LuatOS"
模式对照表
模式 | 含义描述 | 补充说明 |
---|---|---|
'r' | 只读模式打开文件 | • 文件必须存在,否则返回nil • 默认模式,通常不需要显式指定 'b'(二进制模式) |
'r+' | 读写模式打开文件 | • 文件必须存在,否则返回nil • 文件指针位于文件开头 |
'w' | 只写模式打开文件 | • 文件不存在则创建 • 文件存在则截断(清空内容) • 文件指针位于文件开头 |
'w+' | 读写模式打开文件 | • 文件不存在则创建 • 文件存在则截断(清空内容) • 文件指针位于文件开头 |
'a' | 追加模式(只写) | • 文件不存在则创建 • 所有写入自动追加到文件末尾 • 不能修改已有内容(只能追加) |
'a+' | 读取和追加模式 | • 文件不存在则创建 • 可读取文件内容 • 所有写入自动追加到文件末尾 |
'rb' | 二进制只读模式 | • 只读模式,文件必须存在,否则返回nil • 与'r'模式类似,但明确指定二进制模式 |
'r+b', 'rb+' | 二进制读写模式 | • 读写模式,文件必须存在,否则返回nil • 'r+b'和'rb+'完全等效 |
'wb' | 二进制只写模式 | • 只写模式,文件不存在则创建,存在则清空 • 与'w'模式类似,但明确指定二进制模式 |
'w+b', 'wb+' | 二进制读写模式 | • 读写模式,文件不存在则创建,存在则清空 • 'w+b'和'wb+'完全等效 |
'ab' | 二进制追加模式 | • 只追加模式,文件不存在则创建,所有写入追加到末尾 • 与'a'模式类似,但明确指定二进制模式 |
'a+b', 'ab+' | 二进制读写模式 | • 读写模式,文件不存在则创建,可读取文件,写入追加到末尾 • 'a+b'和'ab+'完全等效 |
示例
-- 创建一个测试文件并写入内容
local test_file = "/sd/test_file.txt"
io.writeFile(test_file, "Hello LuatOS! This is a test file with some content.")
-- 1. 读取整个文件
log.info("测试1", "===== 读取整个文件 =====")
local full_data = io.readFile(test_file)
if full_data then
-- 测试1 成功读取整个文件 长度:52 内容:Hello LuatOS! This is a test file with some content.
log.info("测试1", "成功读取整个文件", "长度:" .. #full_data, "内容:" .. full_data)
else
log.info("测试1", "读取整个文件失败")
end
-- 2. 读取部分文件内容(在文件大小范围内)
log.info("测试2", "===== 读取部分文件(范围内) =====")
-- 从第6字节开始读取7字节
local partial_data = io.readFile(test_file, "rb", 6, 7)
if partial_data then
-- 测试2 成功读取部分文件 长度:7 内容:LuatOS!
log.info("测试2", "成功读取部分文件", "长度:" .. #partial_data, "内容:" .. partial_data)
else
log.info("测试2", "读取部分文件失败")
end
-- 3. 尝试读取超出文件大小的范围
log.info("测试3", "===== 读取超出文件大小的范围 =====")
local file_size = io.fileSize(test_file)
-- 测试3 文件大小: 52 字节
log.info("测试3", "文件大小:", file_size, "字节")
-- 3.1 偏移量超出文件大小
local overflow_data1 = io.readFile(test_file, "rb", file_size + 10, 5)
if overflow_data1 then
-- 测试3.1 偏移量超出-返回数据 长度:0 内容:
log.info("测试3.1", "偏移量超出-返回数据", "长度:" .. #overflow_data1, "内容:" .. overflow_data1:toHex())
else
log.info("测试3.1", "偏移量超出-返回nil")
end
-- 3.2 偏移量在文件内,但读取长度超出文件末尾
local overflow_data2 = io.readFile(test_file, "rb", file_size - 5, 10)
if overflow_data2 then
-- 测试3.2 长度超出-返回数据 长度:5 内容:74656E742E
log.info("测试3.2", "长度超出-返回数据", "长度:" .. #overflow_data2, "内容:" .. overflow_data2:toHex())
else
log.info("测试3.2", "长度超出-返回nil")
end
-- 3.3 偏移量和长度都超出文件大小
local overflow_data3 = io.readFile(test_file, "rb", file_size + 5, 10)
if overflow_data3 then
-- 测试3.3 完全超出-返回数据 长度:0 内容:
log.info("测试3.3", "完全超出-返回数据", "长度:" .. #overflow_data3, "内容:" .. overflow_data3:toHex())
else
log.info("测试3.3", "完全超出-返回nil")
end
4.4 io.writeFile(path, data,mode)
功能
将数据写入文件;
注意事项
默认mode是"wb+",完全覆写模式 ,清空原有内容,文件不存在则创建;
参数
path
参数含义:文件系统路径;
数据类型:string;
取值范围:有效文件系统路径;
是否必选:是;
注意事项:暂无;
参数示例:"/bootime";
data
参数含义:要写入的内容;
数据类型:string;
取值范围:任意字符串;
是否必选:是;
注意事项:暂无;
参数示例:"1"
mode
参数含义:文件写入模式;
数据类型:string;
取值范围:"wb+"等,详情参考下面的模式对照表;
是否必选:否;
注意事项:默认"wb+";
参数示例:"wb+";
返回值
local success = io.writeFile(path, data,mode)
有一个返回值 success
success
含义说明:写入操作结果;
数据类型:boolean;
取值范围:true或false;
注意事项:暂无;
返回示例:true;
模式对照表
模式 | 含义描述 | 补充说明 |
---|---|---|
'r' | 只读模式打开文件 | • 文件必须存在,否则返回nil • 默认模式,通常不需要显式指定 'b'(二进制模式) |
'r+' | 读写模式打开文件 | • 文件必须存在,否则返回nil • 文件指针位于文件开头 |
'w' | 只写模式打开文件 | • 文件不存在则创建 • 文件存在则截断(清空内容) • 文件指针位于文件开头 |
'w+' | 读写模式打开文件 | • 文件不存在则创建 • 文件存在则截断(清空内容) • 文件指针位于文件开头 |
'a' | 追加模式(只写) | • 文件不存在则创建 • 所有写入自动追加到文件末尾 • 不能修改已有内容(只能追加) |
'a+' | 读取和追加模式 | • 文件不存在则创建 • 可读取文件内容 • 所有写入自动追加到文件末尾 |
'rb' | 二进制只读模式 | • 只读模式,文件必须存在,否则返回nil • 与'r'模式类似,但明确指定二进制模式 |
'r+b', 'rb+' | 二进制读写模式 | • 读写模式,文件必须存在,否则返回nil • 'r+b'和'rb+'完全等效 |
'wb' | 二进制只写模式 | • 只写模式,文件不存在则创建,存在则清空 • 与'w'模式类似,但明确指定二进制模式 |
'w+b', 'wb+' | 二进制读写模式 | • 读写模式,文件不存在则创建,存在则清空 • 'w+b'和'wb+'完全等效 |
'ab' | 二进制追加模式 | • 只追加模式,文件不存在则创建,所有写入追加到末尾 • 与'a'模式类似,但明确指定二进制模式 |
'a+b', 'ab+' | 二进制读写模式 | • 读写模式,文件不存在则创建,可读取文件,写入追加到末尾 • 'a+b'和'ab+'完全等效 |
示例
io.writeFile("/bootime", "1")
4.5 file:fill(buff, offset, len)
功能
读取文件并填充到zbuff内,但不移动指针位置;
注意事项
1、需要在文件打开后调用;
2、使用zbuff需要先创建zbuff对象;
3、此操作不会改变文件和zbuff的当前读写位置;
参数
buff
参数含义:zbuff对象;
数据类型:userdata(zbuff);
取值范围:已创建的zbuff对象;
是否必选:是;
注意事项:zbuff对象需提前创建并分配足够空间;
参数示例:zbuff.create(1024);
offset
参数含义:zbuff中的起始偏移位置;
数据类型:number;
取值范围:≥0的整数;
是否必选:否;
注意事项:默认为0;
参数示例:0;
len
参数含义:要写入的长度;
数据类型:number;
取值范围:≥0的整数;
是否必选:否;
注意事项:默认为zbuff的剩余长度(从offset开始计算);
参数示例:512;
返回值
local success, read_len = fd:fill(buff, offset, len)
有两个返回值
success
含义说明:操作是否成功;
数据类型:boolean;
取值范围:true或false;
注意事项:暂无;
返回示例:true;
read_len
含义说明:实际填充的长度;
数据类型:number;
取值范围:≥0的整数;
注意事项:如果填充失败,返回的值可能为负数或0;
示例
local buff = zbuff.create(1024)
local f = io.open("/sd/test.txt")
if f then
f:fill(buff)
end
4.6 io.fsstat(path)
功能
获取文件系统状态信息;
注意事项
1、路径参数可选,默认为根目录"/",表示模组内置Flash中的文件系统分区;
2、目前在传入错误路径的情况下,会自动调整为可查的文件系统分区根目录;
3、返回多个值表示文件系统状态信息;
参数
path
参数含义:文件系统路径;
数据类型:string;
取值范围:有效文件系统路径;
是否必选:否;
注意事项:默认值为根目录"/",表示模组内置Flash中的文件系统分区,若路径无效可能返回失败;
参数示例:"/"
返回值
local success, total_blocks, used_blocks, block_size, fs_type = io.fsstat(path);
存在五个返回值:success, total_blocks, used_blocks, block_size, fs_type;
success
含义说明:操作是否成功;
数据类型:boolean;
取值范围:true 或 false;
注意事项:获取成功返回true,否则返回false;
返回示例:true
total_blocks
含义说明:总的block(块)数量;
数据类型:number;
取值范围:≥0的整数;
注意事项:文件系统总容量指标,失败时第一个返回值success为false0,每个块的大小都是一致的;
返回示例:244264960
used_blocks
含义说明:已使用的block(块)数量;
数据类型:number;
取值范围:≥0的整数;
注意事项:文件系统已使用容量指标,失败时第一个返回值success为false,每个块的大小都是一致的;
返回示例:2304
block_size
含义说明:block(内存块)的大小;
数据类型:number;
取值范围:≥0的整数;
注意事项:单位:字节;
返回示例:512
fs_type
含义说明:文件系统类型;
数据类型:string;
取值范围:文件系统标识字符串,如"lfs","fatfs";
注意事项:理论上失败时返回空字符串"",目前实际会自动识别能够识别到的文件系统类型;
返回示例:"fatfs"
示例
-- ########## 获取SD卡的可用空间信息并打印。 ##########
local data, err = fatfs.getfree("/sd")
if data then
-- 打印SD卡的可用空间信息
--fatfs getfree {"free_sectors":244263168,"total_kb":122132480,"free_kb":122131584,"total_sectors":244264960}
log.info("fatfs", "getfree", json.encode(data))
-- 创建目录
if io.mkdir(dir_path) then
log.info("io.mkdir", "目录创建成功", "路径:" .. dir_path)
-- 创建并写入文件
local file_path = dir_path .. "/boottime"
local file = io.open(file_path, "wb")
if file then
file:write("这是io库API文档示例的测试内容")
file:close()
-- 在LuatOS文件操作中,执行file:close()是必须且关键的操作,它用于关闭文件句柄,释放资源,并确保数据被正确写入磁盘。
-- 如果不执行file:close(),可能会导致数据丢失、文件损坏或其他不可预测的问题。
log.info("文件创建", "文件写入成功", "路径:" .. file_path)
else
-- === fs函数测试开始 ===
log.info("=== fs函数测试开始 ===")
-- 测试io.fsstat
-- 日志打印io.fsstat成功: 总空间=244264960块 已用=2304块 块大小=512字节 类型=fatfs
log.info("测试io.fsstat('/sd'):")
local success, total_blocks, used_blocks, block_size, fs_type = io.fsstat("/sd")
if ok then
log.info("io.fsstat成功:",
"总空间=" .. total_blocks .. "块",
"已用=" .. used_blocks .. "块",
"块大小=" .. used_blocks.."字节",
"类型=" .. fs_type)
else
-- 错误信息在total中
log.info("io.fsstat失败:", total)
end
--[[
我使用的是128GB的新的sd卡,我们验证下数据,fatfs.getfree 得到的空间信息是否正确:
total_sectors = 244,264,960 个扇区
每个扇区通常是 512 字节(这是FAT文件系统的标准)
总容量 = 244,264,960 × 512 = 125,063,659,520 字节
转换为KB = 125,063,659,520 / 1024 = 122,132,480 KB (与日志中的 total_kb 一致)
转换为GB = 122,132,480 / 1024 / 1024 ≈ 116.5 GB
128GB TF卡显示为116.5GB是正常的,因为:
1.厂商使用的十进制计算(1GB = 10⁹字节)vs 系统使用的二进制计算(1GB = 2³⁰字节)
2.文件系统本身会占用一些空间
3.可能有隐藏的系统分区
io.fsstat('/sd') 给出的空间是否正确?
总块数 = 244,264,960
块大小 = 512 字节
总容量 = 244,264,960 × 512 = 125,063,659,520 字节 ≈ 116.47GB
已用块数 = 2,304
已用空间 = 2,304 × 512 = 1,179,648 字节 ≈ 1.15 MB
这与 fatfs.getfree 的结果一致
]]
4.7 io.dexist(path)
功能
判断目录是否存在;
注意事项
1、专用于检查目录是否存在;
2、与io.exists()区别在于专门针对目录;
3、Air780系列Air和8000系列,在不低于 V2014 的固件才能支持该接口;
4、Air8101系列和Air6101系列,在不低于V1004 的固件才能支持该接口;
参数
path
参数含义:目录路径;
数据类型:string;
取值范围:有效文件系统路径;
是否必选:是;
注意事项:暂无;
参数示例:"/sd/myf";
返回值
local exists = io.dexist(path)
有一个返回值 exists
exists
含义说明:目录存在状态;
数据类型:boolean;
取值范围:true或false;
注意事项:暂无;
返回示例:true;
示例
log.info("io", "dir存在吗?", io.dexist("/sd/myf"))
4.8 io.mkfs(path)
功能
格式化指定挂载点的文件系统;
注意事项
此操作会清除指定挂载点上的所有数据,请谨慎使用;
参数
path
参数含义:要格式化的挂载点路径;
数据类型:string;
取值范围:有效的已挂载文件系统路径;
是否必选:是;
注意事项:必须是已挂载的文件系统路径;
参数示例:"/sd";
返回值
local success, errio = io.mkfs(path)
有两个返回值
success
含义说明:格式化操作结果;
数据类型:boolean;
取值范围:true或false;
注意事项:暂无;
返回示例:true;
errio
含义说明:底层错误码;
数据类型:number;
取值范围:整数;
注意事项:失败时返回底层错误码,成功时为0;
返回示例:0
返回码对照表
Lua返回值 | 含义说明 | 常见原因及处理建议 |
---|---|---|
0 | 格式化成功 | 格式化操作顺利完成 |
-1 | 底层磁盘I/O硬件错误 | 存储设备物理损坏或连接问题,检查硬件连接 |
-2 | 断言失败(内部错误) | FatFs内部逻辑错误,可能需要重启系统 |
-3 | 物理驱动器未就绪 | 存储设备未插入或未初始化,检查设备连接 |
-6 | 路径名格式无效 | 挂载点路径格式错误,检查路径格式 |
-7 | 访问被拒绝 | 权限不足或存储设备已满,检查权限和空间 |
-10 | 物理驱动器被写保护 | 存储设备写保护开关打开,关闭写保护 |
-11 | 逻辑驱动器号无效 | 驱动器号配置错误,检查驱动器配置 |
-12 | 卷没有工作区 | 文件系统未挂载或初始化失败,检查挂载状态 |
-14 | 格式化过程中止 | 驱动器容量太小、簇大小设置不当或FAT类型不兼容 |
-15 | 无法控制卷(超时) | 设备响应超时,检查设备状态或重试 |
-19 | 给定参数无效 | MKFS_PARM结构体参数错误、格式选项冲突或缓冲区无效 |
示例
local success, errio = io.mkfs("/sd")
-- 输出true 0
log.info("fs", "mkfs",success, errio)
4.9 io.mkdir(path)
功能
创建文件夹;
注意事项
暂无;
参数
path
参数含义:目录路径;
数据类型:string;
取值范围:有效文件系统路径;
是否必选:是;
注意事项:暂无;
参数示例:"/data/";
返回值
local success, errio = io.mkdir(path)
有两个返回值
success
含义说明:创建目录结果;
数据类型:boolean;
取值范围:true或false;
注意事项:暂无;
返回示例:true;
errio
含义说明:返回码;
数据类型:number;
取值范围:整数;
注意事项:成功返回0,出现错误返回其他number返回值;
返回示例:-2
返回码对照表
返回码 | 含义说明 | 常见场景 |
---|---|---|
0 | 函数执行成功 | 目录创建成功 |
-1 | 底层磁盘I/O发生硬件错误 | 存储设备物理损坏或连接问题 |
-2 | 断言失败(内部错误) | FatFs内部逻辑错误 |
-3 | 物理驱动器未就绪 | TF卡未插入或未初始化 |
-5 | 找不到路径(父目录不存在) | 尝试创建多级目录但父目录不存在 |
-6 | 路径名格式无效 | 路径包含非法字符或格式错误 |
-7 | 访问被拒绝 | 权限不足或目录已满 |
-8 | 访问被拒绝(已存在同名文件/目录) | 目录名称已存在 |
-9 | 文件/目录对象无效 | 文件系统结构损坏 |
-10 | 物理驱动器被写保护 | TF卡写保护开关打开 |
-11 | 逻辑驱动器号无效 | 驱动器号配置错误 |
-12 | 卷没有工作区 | 文件系统未挂载或初始化失败 |
-13 | 找不到有效的FAT卷 | 存储设备未格式化或文件系统损坏 |
-15 | 在定义的时间内无法控制卷 | 设备响应超时 |
-16 | 根据文件共享策略拒绝操作 | 文件或目录被锁定 |
-17 | LFN工作缓冲区无法分配 | 内存不足,无法处理长文件名 |
-18 | 打开文件数量超过限制 | 系统文件句柄耗尽 |
-19 | 给定参数无效 | 传入参数格式或值错误 |
示例
local success, errno = io.mkdir("/sd/test_dir/")
if success then
-- 输出mkdir成功 0
log.info("mkdir成功",errno)
io.rmdir("/fatfs/test_dir/") -- 清理
else
log.info("mkdir失败", "错误码:", errno)
end
4.10 io.rmdir(path)
功能
删除目录;
注意事项
只能删除空目录,如删除非空目录会删除失败;
参数
path
参数含义:目录路径;
数据类型:string;
取值范围:有效文件系统路径;
是否必选:是;
注意事项:目录必须为空;
参数示例:"/data/";
返回值
local success, errio = io.rmdir(path)
有两个返回值
success
含义说明:删除目录结果;
数据类型:boolean;
取值范围:true或false;
注意事项:暂无;
返回示例:true;
errio
含义说明:底层返回码;
数据类型:number;
取值范围:整数;
注意事项:成功返回0,失败时返回底层错误码;
返回示例:0
返回码 | 含义说明 | 常见场景 |
---|---|---|
0 | 函数执行成功 | 目录删除成功 |
-1 | 底层磁盘I/O发生硬件错误 | 存储设备物理损坏或连接问题 |
-2 | 断言失败(内部错误) | FatFs内部逻辑错误 |
-3 | 物理驱动器未就绪 | TF卡未插入或未初始化 |
-4 | 找不到文件/目录 | 目录不存在 |
-5 | 找不到路径(父路径不存在) | 父目录不存在 |
-6 | 路径名格式无效 | 路径包含非法字符或格式错误 |
-7 | 访问被拒绝(目录非空、写保护等) | 目录非空 |
-8 | 访问被拒绝(已存在冲突) | 名称冲突 |
-9 | 文件/目录对象无效 | 文件系统结构损坏 |
-10 | 物理驱动器被写保护 | TF卡写保护开关打开 |
-11 | 逻辑驱动器号无效 | 驱动器号配置错误 |
-12 | 卷没有工作区 | 文件系统未挂载或初始化失败 |
-13 | 找不到有效的FAT卷 | 存储设备未格式化或文件系统损坏 |
-15 | 在定义时间内无法控制卷 | 设备响应超时 |
-16 | 根据文件共享策略拒绝操作 | 文件或目录被锁定 |
-17 | LFN工作缓冲区无法分配 | 内存不足,无法处理长文件名 |
-18 | 打开文件数量超过限制 | 系统文件句柄耗尽 |
-19 | 给定参数无效 | 传入参数格式或值错误 |
示例
local test_dir = "/sd/test_directory/"
log.info("rmdir演示", "===== 步骤1: 创建测试目录 =====")
local mk_success, mk_err = io.mkdir(test_dir)
if mk_success then
log.info("rmdir演示", "目录创建成功", test_dir)
else
log.error("rmdir演示", "目录创建失败", mk_err)
return
end
-- 2. 验证目录存在
log.info("rmdir演示", "===== 步骤2: 验证目录存在 =====")
if io.dexist(test_dir) then
log.info("rmdir演示", "目录存在验证成功")
else
log.error("rmdir演示", "目录存在验证失败")
return
end
-- 3. 删除目录
log.info("rmdir演示", "===== 步骤3: 删除目录 =====")
local rm_success, rm_err = io.rmdir(test_dir)
if rm_success then
log.info("rmdir演示", "目录删除成功")
else
log.error("rmdir演示", "目录删除失败", "错误码:", rm_err)
end
-- 4. 验证目录已删除
log.info("rmdir演示", "===== 步骤4: 验证目录已删除 =====")
if not io.dexist(test_dir) then
log.info("rmdir演示", "目录删除验证成功")
else
log.error("rmdir演示", "目录删除验证失败")
end
4.11 io.lsdir(path, max_cnt, offset)
功能
列出目录下的文件;
注意事项
1、需要指定目录存在且有读取权限;
2、支持分页机制,避免一次性返回大量数据占用过多内存;
3、返回的文件信息包含名称、大小和类型等数据;
4、最大返回数量有限制,最大100条;
5、返回错误码的常见错误包括目录不存在(-5)、权限不足(-7)和参数无效(-19);
参数
path
参数含义:要枚举的目录路径;
数据类型:string;
取值范围:有效的目录路径;
是否必选:是;
注意事项:目录必须存在且有读取权限;
参数示例:"/sd/data/";
max_cnt
参数含义:最大返回数量;
数据类型:number;
取值范围:1——100的整数,小于0时强制为10,大于100时,强制为100;
是否必选:否;
注意事项:默认10,最大不超过100,如果当目录文件超过100条时,可以通过 offset 参数进行分页查询;
-- 第一页,获取前100条
local ret1, data1 = io.lsdir("/path/", 100, 0)
-- 第二页,从第100条开始获取接下来的100条
local ret2, data2 = io.lsdir("/path/", 100, 100)
-- 第三页,从第200条开始获取
local ret3, data3 = io.lsdir("/path/", 100, 200)
参数示例:20;
offset
参数含义:查询起始偏移量;
数据类型:number;
取值范围:≥0的整数;
是否必选:否;
注意事项:默认0,用于分页查询;
参数示例:0;
返回值
local success, data = io.lsdir(path, max_cnt, offset)
有两个返回值
success
含义说明:操作是否成功;
数据类型:boolean;
取值范围:true或false;
注意事项:暂无;
返回示例:true;
data
含义说明:目录内容信息或错误码;
数据类型:table或number,当为table时,表结构为 {"name": "文件名称", "size": 文件大小, "type": 文件类型},说明如下;
{
-- 含义说明:文件或目录的名称;
-- 数据类型:string;
-- 取值范围:有效的文件名或目录名;
-- 注意事项:暂无;
-- 返回示例:"boottime", "testline", "renamed_file.txt";
name = ,
-- 含义说明:文件大小(字节);
-- 数据类型:number;
-- 取值范围:≥0的整数;
-- 注意事项:暂无;
-- 返回示例:0, 1024, 2048;
size = ,
-- 含义说明:文件类型标识;
-- 数据类型:number;
-- 取值范围:0或1;
-- 注意事项:0代表普通文件,1代表文件夹;
-- 返回示例:0;
type = ,
取值范围:成功时返回文件信息表,失败时返回错误码;
注意事项:成功时返回包含文件信息的数组,每个元素包含name、size、type等字段;
返回示例:ret, data = io.lsdir(dir_path, 50, 0) -- 50表示最多返回50个文件,0表示从目录开头开始
if ret then
log.info("fs", "lsdir", json.encode(data))
错误码对照表
数值 (Lua) | 含义说明 | 排查建议 |
---|---|---|
-1 | 底层磁盘I/O硬件错误 | 1. 检查存储设备物理连接 2. 尝试重新插拔存储设备 3. 检查存储设备是否损坏 |
-2 | 断言失败(内部错误) | 1. 重启设备 2. 检查系统固件版本 3. 查看系统日志获取更多信息 |
-3 | 物理驱动器未就绪 | 1. 确认存储设备已正确插入 2. 检查设备初始化是否正确 3. 等待设备就绪后重试 |
-4 | 找不到文件 | 1. 确认文件是否存在 2. 检查路径是否正确 3. 确认文件未被删除或移动 |
-5 | 找不到路径(目录不存在) | 1. 确认目录路径是否正确 2. 使用 io.dexist() 检查目录是否存在 3. 确认大小写是否正确 |
-6 | 路径名格式无效 | 1. 检查路径格式是否符合规范 2. 避免使用非法字符 3. 检查路径长度是否超限 |
-7 | 访问被拒绝(权限不足或目录已满) | 1. 检查文件/目录权限设置 2. 确认存储空间是否充足 3. 检查文件是否被其他进程占用 |
-8 | 文件/目录已存在 | 1. 确认是否重复创建 2. 检查文件名冲突 3. 如需覆盖先删除原有文件 |
-9 | 文件/目录对象无效 | 1. 文件系统可能损坏 2. 尝试修复文件系统 3. 重启设备后重试 |
-10 | 物理驱动器被写保护 | 1. 检查存储设备的写保护开关 2. 确认设备是否只读模式 3. 更换存储设备 |
-11 | 逻辑驱动器号无效 | 1. 检查驱动器号配置 2. 确认文件系统已正确挂载 3. 重新挂载文件系统 |
-12 | 卷没有工作区 | 1. 检查文件系统是否初始化 2. 确认挂载点配置正确 3. 重新挂载文件系统 |
-13 | 找不到有效的FAT卷 | 1. 确认存储设备已格式化 2. 检查文件系统类型是否支持 3. 可能需要重新格式化 |
-14 | 格式化过程中止 | 1. 检查存储设备容量 2. 确认格式化参数正确 3. 尝试使用不同格式化选项 |
-15 | 操作超时 | 1. 检查设备响应状态 2. 增加超时等待时间 3. 确认设备工作正常 |
-16 | 操作被锁定(文件共享冲突) | 1. 关闭可能占用文件的进程 2. 等待文件解锁 3. 重启设备解除锁定 |
-17 | 内存不足 | 1. 关闭不必要的应用程序 2. 优化内存使用 3. 增加系统内存 |
-18 | 打开文件数量超过限制 | 1. 关闭不再使用的文件 2. 增加系统文件打开数限制 3. 优化文件使用流程 |
-19 | 参数无效 | 1. 检查传入参数是否符合要求 2. 确认参数值在有效范围内 3. 检查参数类型是否正确 |
示例
-- 1. 定义测试目录路径
local dir_path = "/sd/test_directory/"
-- 2. 创建测试目录
local mkdir_success, mkdir_err = io.mkdir(dir_path)
if not mkdir_success
then
log.error("main", "创建目录失败", mkdir_err)
return
end
log.info("main", "目录创建成功", dir_path)
-- 3. 创建三个测试文件并写入内容
-- 文件1: boottime (6字节)
local boottime_content = "123456"
local boottime_path = dir_path .. "boottime"
if io.writeFile(boottime_path, boottime_content)
then
log.info("main", "创建文件成功", boottime_path, "大小:", #boottime_content)
else
log.error("main", "创建文件失败", boottime_path)
end
-- 文件2: testline (47字节)
local testline_content = "This is a test file with exactly forty-seven bytes!!"
local testline_path = dir_path .. "testline"
if io.writeFile(testline_path, testline_content)
then
log.info("main", "创建文件成功", testline_path, "大小:", #testline_content)
else
log.error("main", "创建文件失败", testline_path)
end
-- 文件3: renamed_file.txt (0字节 - 空文件)
local renamed_path = dir_path .. "renamed_file.txt"
-- 写入空字符串创建0字节文件
if io.writeFile(renamed_path, "")
then
log.info("main", "创建文件成功", renamed_path, "大小: 0")
else
log.error("main", "创建文件失败", renamed_path)
end
local success, data = io.lsdir(dir_path, 50, 0) -- 50表示最多返回50个文件,0表示从目录开头开始
if success then
-- 输出fs lsdir {"name":"boottime","size":6,"type":0},{"name":"testline","size":47,"type":0},{"name":"renamed_file.txt","size":0,"type":0}
log.info("fs", "lsdir", json.encode(data))
else
log.info("fs", "lsdir", "fail", success, data)
end
4.12 io.lsmount()
功能
列出所有挂载点;
注意事项
1、可用于检查某个挂载点是否存在文件系统;
2、不区分文件系统类型;
参数
无
返回值
local data = io.lsmount()
有一个返回值 data
data
含义说明:挂载信息列表,表中包含一个或者多个元素,元素的具体数量取决于当前系统挂载情况;
表中的每个元素仍然是一个表,元素的格式为: {"fs": "文件系统类型", "path": "挂载点路径"},说明如下;
{
-- 含义说明:文件系统类型;
-- 数据类型:string;
-- 取值范围:文件系统类型标识;
-- 注意事项:暂无;
-- 返回示例:"fatfs";
fs = ,
-- 含义说明:文件系统挂载点路径;
-- 数据类型:string;
-- 取值范围:文件系统挂载点路径;
-- 注意事项:暂无;
-- 返回示例:"/sd/";
path = ,
}
数据类型:table;
取值范围:包含挂载信息的表;
注意事项:每个元素包含文件系统类型和路径;
返回示例:local data = io.lsmount()
-- 输出fs lsmount [{"fs":"ec7xx","path":""},{"fs":"inline","path":"\/lua\/"},{"fs":"ram","path":"\/ram\/"},{"fs":"luadb","path":"\/luadb\/"},{"fs":"fatfs","path":"\/sd"}]
log.info("fs", "lsmount", json.encode(data))
示例
local data = io.lsmount()
-- 输出fs lsmount [{"fs":"ec7xx","path":""},{"fs":"inline","path":"\/lua\/"},{"fs":"ram","path":"\/ram\/"},{"fs":"luadb","path":"\/luadb\/"},{"fs":"fatfs","path":"\/sd"}]
log.info("fs", "lsmount", json.encode(data))
4.13 io.open(path, mode)
功能
打开文件并返回文件句柄;
注意事项
1、文件打开后必须调用fd:close()关闭;
2、不同模式影响文件读写权限和初始位置;
3、刷机时添加的文件位于/luadb目录下,且为只读;
参数
path
- 参数含义:文件路径;
- 数据类型:string;
- 取值范围:有效文件系统路径;
- 是否必选:是;
- 注意事项:暂无;
- 参数示例:"/xxx.txt";
mode
参数含义:文件访问模式;
数据类型:string;
取值范围:"r"等,详情参考下面的模式对照表;
是否必选:否(默认为"r");
注意事项:"r"-只读,"r+"-读写,"w"-写入(清空),"w+"-读写(清空),"a"-追加,"a+"-读写追加;
参数示例:"rb";
返回值
local fd = io.open(path, mode)
有一个返回值fd
fd
含义说明:文件操作句柄;
数据类型:userdata/nil;
取值范围:成功时,返回非nil的userdata,包含有效的文件句柄信息,失败则返回nil;
注意事项:成功后,该文件使用结束后,必须调用fd:close()关闭;
返回示例: file (0x2001c8f0);
模式对照表
模式 | 含义描述 | 补充说明 |
---|---|---|
'r' | 只读模式打开文件 | • 文件必须存在,否则返回nil • 默认模式,通常不需要显式指定 'b'(二进制模式) |
'r+' | 读写模式打开文件 | • 文件必须存在,否则返回nil • 文件指针位于文件开头 |
'w' | 只写模式打开文件 | • 文件不存在则创建 • 文件存在则截断(清空内容) • 文件指针位于文件开头 |
'w+' | 读写模式打开文件 | • 文件不存在则创建 • 文件存在则截断(清空内容) • 文件指针位于文件开头 |
'a' | 追加模式(只写) | • 文件不存在则创建 • 所有写入自动追加到文件末尾 • 不能修改已有内容(只能追加) |
'a+' | 读取和追加模式 | • 文件不存在则创建 • 可读取文件内容 • 所有写入自动追加到文件末尾 |
'rb', 'r+b', 'rb+' | 二进制模式组合 | • 在LuatOS中,指定'b'表示以二进制模式打开(但LuatOS默认就是二进制模式) • 注意:'r+b'和'rb+'是等效的,都表示读写模式(二进制) |
'wb', 'w+b', 'wb+' | 二进制模式组合 | • 同上,二进制模式 |
'ab', 'a+b', 'ab+' | 二进制模式组合 | • 同上,二进制模式 |
示例
-- 只读模式打开文件
local fd = io.open("/xxx.txt", "rb")
if fd then
-- 文件操作...
fd:close()
end
4.14 fd:read(format1,...)
功能
从文件中读取数据;
注意事项
1、需要在文件打开后调用
2、读取模式影响返回数据的格式
3、可以传入多个格式参数,返回多个值
参数
format1
参数含义:数据读取模式;
数据类型:string/number;
取值范围:"*l", "*a", "*L"或数字字节数,参考下面的格式对照表;
是否必选:否(不传参数默认为"*l");
注意事项:"*l"-按行读取(不含换行符),"*a"-读取全部,"*L"-按行读取(含换行符),数字-读取指定字节数;
参数示例:12 或 "*l";
...
参数含义:额外的读取格式参数;
数据类型:string/number;
取值范围:与 format1 相同的格式选项;
是否必选:否;
注意事项:
1.支持传入多个格式参数,按顺序执行读取操作;
2.每个参数对应一个返回值;
参数示例:"l", 10, "*a"
格式对照表
参数格式 | 含义说明 | 示例 | 返回值 |
---|---|---|---|
"l" | 读取一行(不包含行尾的换行符 \n) | fd:read("l") | 字符串(无换行符) |
"L" | 读取一行(包含行尾的换行符 \n) | fd:read("L") | 字符串(包含换行符) |
"*a" | 读取整个文件的剩余所有内容 | fd:read("*a") | 字符串 |
数字 n | 读取 n 个字节的数据 | fd:read(10) | 字符串(最多 n 字节) |
(无参数) | 默认行为,等同于 fd:read("l"),即读取一行(不包含换行符) | fd:read() | 字符串(无换行符) |
多参数组合 | 支持依次传入多个参数,按顺序读取。返回多个值,分别对应每个参数的读取结果 | fd:read("l", 10, "*a") | 多个返回值(一行字符串, 10字节字符串, 剩余内容字符串) |
返回值说明
1、返回值数量:根据传入的参数数量决定,每个参数对应一个返回值;
2、返回值类型: string 或 nil;
3、结束条件:当读取到文件末尾时,后续返回值均为 nil
单参数调用情况
1、 格式参数:"l"(读取一行,不包含换行符)
返回值
local line = fd:read("l")
有一个返回值 line
line
含义说明:从文件中读取的一行内容,不包含行尾换行符;
数据类型:string/nil;
取值范围:成功时返回字符串,到达文件末尾时返回 nil;
注意事项:会自动去除行尾的换行符(\n 或 \r\n);
返回示例:"hello world";
2、格式参数:"L"(读取一行,包含换行符)
返回值
local line = fd:read("L")
有一个返回值 line
line
含义说明:从文件中读取的一行内容,包含行尾换行符;
数据类型:string/nil;
取值范围:成功时返回字符串,到达文件末尾时返回 nil;
注意事项:保留行尾的换行符字符;
返回示例:"hello world\n";
3、格式参数:"*a"(读取整个文件内容)
返回值
local content = fd:read("*a")
有一个返回值 content
content
含义说明:从当前文件指针位置到文件末尾的全部内容;
数据类型:string;
取值范围:可能为空字符串或包含文件内容的字符串;
注意事项:会读取所有剩余内容,可能导致内存占用较大;
返回示例:"这是文件的全部内容";
4、格式参数:数字 n(读取指定字节数)
返回值
local data = fd:read(10)
有一个返回值 data
data
含义说明:从文件中读取的指定字节数的数据;
数据类型:string/nil;
取值范围:成功时返回字符串,如果请求长度大于实际长度,只返回到达文件尾的数据,末尾时返回 nil;
注意事项:实际读取的字节数可能小于请求的字节数;
返回示例:"1234567890";
5、无参数调用(默认读取一行)
返回值
local line = fd:read()
有一个返回值 line
line
含义说明:从文件中读取的一行内容,不包含行尾换行符;
数据类型:string/nil;
取值范围:成功时返回字符串,到达文件末尾时返回 nil;
注意事项:等同于 fd:read("l");
返回示例:"default line";
多参数调用情况
格式:多个参数组合
返回值
local val1, val2, val3 = fd:read("l", 10, "*a")
有多个返回值,数量与参数数量相同
val1
含义说明:第一个参数的读取结果;
数据类型:string/nil;
取值范围:根据第一个参数格式决定;
注意事项:对应 "l" 格式的读取结果;
返回示例:"first line"
val2
含义说明:第二个参数的读取结果;
数据类型:string/nil;
取值范围:根据第二个参数格式决定;
注意事项:对应数字格式的读取结果;
返回示例:"1234567890"
val3
含义说明:第三个参数的读取结果;
数据类型:string/nil;
取值范围:根据第三个参数格式决定;
注意事项:对应 "*a" 格式的读取结果;
返回示例:"remaining content";
示例
-- fd:read(format) 不同返回值验证示例
log.info("fd:read(format) 验证示例", "===== 开始测试 =====")
-- 创建测试文件
local test_file = "/test_read_formats.txt"
local test_content = "第一行数据\n第二行数据\n第三行数据\n文件末尾"
io.writeFile(test_file, test_content)
log.info("测试文件创建完成", "内容包含多行数据")
-- 打开文件
local f = io.open(test_file, "r")
if f then
log.info("文件打开成功", test_file)
-- 测试1: 按行读取(*l) - 不含换行符
log.info("测试1", "===== fd:read('*l') 按行读取 =====")
local line1 = f:read("*l")
-- 第一行 内容:第一行数据 类型:string
log.info("第一行", "内容:" .. tostring(line1), "类型:" .. type(line1))
local line2 = f:read("*l")
-- 第二行 内容:第二行数据 类型:string
log.info("第二行", "内容:" .. tostring(line2), "类型:" .. type(line2))
-- 测试2: 按行读取(*L) - 含换行符
log.info("测试2", "===== fd:read('*L') 含换行符读取 =====")
local line_with_nl = f:read("*L")
含换行符行 内容:第三行数据 类型:string
log.info("含换行符行", "内容:" .. tostring(line_with_nl), "类型:" .. type(line_with_nl))
-- 测试3: 读取指定字节数
log.info("测试3", "===== fd:read(5) 读取5个字节 =====")
local bytes = f:read(5)
-- 5个字节 内容:文字 类型:string 长度:5
log.info("5个字节", "内容:" .. tostring(bytes), "类型:" .. type(bytes), "长度:" .. (bytes and #bytes or 0))
-- 测试4: 读取全部(*a)
log.info("测试4", "===== fd:read('*a') 读取剩余全部 =====")
local remaining = f:read("*a")
-- 剩余内容 内容:� 末尾 类型:string 长度:7
log.info("剩余内容", "内容:" .. tostring(remaining), "类型:" .. type(remaining), "长度:" .. (remaining and #remaining or 0))
-- 测试5: 文件末尾返回nil
log.info("测试5", "===== 文件末尾返回nil =====")
local eof = f:read("*l")
-- 文件末尾 返回值:nil 类型:nil
log.info("文件末尾", "返回值:" .. tostring(eof), "类型:" .. type(eof))
-- 测试6: 多格式参数 - 返回多个值
log.info("测试6", "===== 多格式参数返回多个值 =====")
f:seek("set", 0) -- 回到文件开头
local val1, val2, val3 = f:read("*l", 3, "*l")
-- 多返回值 值1:第 一行数据 值2:第 值3:二行数据
log.info("多返回值", "值1:" .. tostring(val1), "值2:" .. tostring(val2), "值3:" .. tostring(val3))
f:close()
log.info("文件关闭完成")
else
log.error("文件打开失败", test_file)
end
4.15 fd:write(data)
功能
向文件写入数据;
注意事项
1、需要在写入或追加模式下打开文件;
2、数据需要是字符串格式;
参数
data
参数含义:写入文件的内容;
数据类型:string;
取值范围:任意字符串;
是否必选:是;
注意事项:Lua字符串可包含二进制数据;
参数示例:"Hello World";
返回值
local result, err_code = fd:write(data)
成功时有三个返回值 result 和 err_code
result
含义说明:返回fd(文件句柄)本身,用于链式调用;
数据类型:userdata/nil;
取值范围:成功始终返回调用该方法的文件句柄对象本身,失败则会固定返回 nil以及错误码,详情请参考下方的错误码对照表;
注意事项:支持链式调用:fd:write("a"):write("b");
返回示例:file (0x2001c8f0) (与原始文件句柄相同的对象)
err_code
含义说明:错误码;
数据类型:number;
取值范围:标准POSIX错误码,如22表示参数无效,详情请参考下方的错误码对照表;
注意事项:失败时返回,固定为第2个返回值,对应POSIX错误码,详情参考下方的错误码对照表;
返回示例:9
错误码对照表
错误码 | 错误信息 | 含义说明 |
---|---|---|
9 | "Bad file descriptor" | 无效的文件描述符 |
5 | "I/O error" | 输入输出错误 |
28 | "No space left on device" | 设备空间不足 |
13 | "Permission denied" | 权限不足 |
2 | "No such file or directory" | 文件或目录不存在 |
22 | "Invalid argument" | 无效参数 |
30 | "Read-only file system" | 只读文件系统 |
示例
local fd = io.open("/test.txt", "wb")
if fd then
fd:write("Hello World")
-- 写入二进制数据
fd:write(string.char(0x12, 0x13))
-- 链式调用
fd:write("Line 1"):write("Line 2"):write("Line 3")
fd:close()
end
4.16 fd:seek(offset, base)
功能
移动文件指针位置;
注意事项
1、改变后续读写操作的起始位置
2、三种基准位置满足不同需求
参数
offset
参数含义:偏移量;
数据类型:number;
取值范围:整数;
是否必选:是;
注意事项:相对于基准位置的偏移字节数;
参数示例:1024;
base
参数含义:移动基准位置;
数据类型:string/number;
取值范围:"set", "cur", "end",io.SEEK_SET , io.SEEK_CUR , io.SEEK_END;
是否必选:否(默认为"cur");
注意事项:可用字符串:"set"-文件开头,"cur"-当前位置,"end"-文件末尾;
可用常量: io.SEEK_SET-文件开头 ,io.SEEK_CUR-当前位置 ,io.SEEK_END-文件尾;
参数示例:io.SEEK_SET
返回值
local new_pos,err_str, err_code = fd:seek(offset, base)
成功时有一个返回值new_pos,err_str, err_code
new_pos
含义说明:移动后文件指针的新位置(从文件开头计算的字节偏移量);
数据类型:number/nil;
取值范围:≥0的整数,表示从文件开头开始的字节偏移量;
注意事项:1、成功时返回新的文件位置;
2、如果移动失败(如超出文件范围),会固定返回 nil以及错误码和字符串内容;
返回示例:1024(表示文件指针现在位于第1024字节处);
err_str
含义说明:错误描述信息;
数据类型:string;
取值范围:系统提供的错误码字符串;
注意事项:仅失败时返回,格式为"错误码"或"文件名: 错误码",固定为第二个返回值;
返回示例:"Invalid argument"
err_code
含义说明:错误码;
数据类型:number;
取值范围:标准POSIX错误码,如22表示参数无效,详情请参考下方的错误码对照表;
注意事项:仅失败时返回,固定为第三个返回值;
返回示例:22(表示参数无效错误)
错误码对照表
错误码 | 错误信息(string) | 含义 |
---|---|---|
EINVAL (22) | "Invalid argument" | 参数无效,如 base 参数不是 "set", "cur", "end" 或对应的常量 |
EOVERFLOW (75) | "Value too large for defined data type" | 偏移量超出文件大小限制 |
ESPIPE (29) | "Illegal seek" | 文件不支持 seek 操作 |
示例
local fd = io.open("/test.txt", "rb")
if fd then
-- 移动到1024字节处
local pos1 = fd:seek(1024, "set")
-- 从当前位置向后移动1024字节
local pos2 = fd:seek(1024, "cur")
-- 从文件末尾向前移动124字节
local pos3 = fd:seek(-124, "end")
fd:close()
end
4.17 fd:close()
功能
关闭文件句柄;
注意事项
1、必须调用以释放资源;
2、未关闭可能导致数据丢失或资源泄漏;
参数
无
返回值
无
示例
-- 只读模式打开文件
local fd = io.open("/xxx.txt", "rb")
if fd then
-- 文件操作...
-- 关闭文件句柄;
fd:close()
end
五、产品支持说明
支持 LuatOS 开发的所有产品都支持io核心库。