跳转至

03 文件系统(io)

作者:王棚嶙

一、概述

io库是LuatOS提供的文件操作核心库,该库提供了完整的文件读写、目录管理、文件系统操作等功能,支持二进制和文本模式的文件操作,能够处理各种存储设备上的文件系统,包括TF卡、SPI Flash等。io库与os库配合使用可以完成绝大多数文件系统操作需求。

1.1 存储系统概述

LuatOS的存储系统采用清晰的分层架构设计,其核心目标是为应用层提供统一、简洁的文件操作接口,从而屏蔽底层不同存储介质和文件系统的差异。开发者无需关心数据具体存储在何种硬件上,也无需关心底层文件系统的具体实现,只需调用统一的API即可完成文件操作。

该系统的层次结构与各组件间的协作关系如下图所示,其数据流与依赖关系自上而下贯穿各层:

image.png

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 文章内容引用

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 如何提高文件操作的效率?

  • 对于大文件操作,可以考虑使用流式处理或分块处理来减少内存占用。
  • 使用合适的文件打开模式(如只读、只写等)以减少不必要的操作。

八、扩展

  • 文件加密与解密:可以结合加密库对文件进行加密存储和解密读取,以提高数据的安全性。
  • 文件备份与恢复:可以定期将文件备份到外部存储设备或云存储中,并在需要时恢复文件。
  • 文件系统监控:可以监控文件系统的变化(如文件创建、删除、修改等),以便及时响应和处理。