跳转至

io - 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'等效)

二、核心示例

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对象
数据类型:userdatazbuff);
取值范围:已创建的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 = ,
取值范围:成功时返回文件信息表,失败时返回错误码;
注意事项:成功时返回包含文件信息的数组,每个元素包含namesizetype等字段
返回示例: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核心库。