模块: 文件系统
约 6028 字大约 20 分钟
2024-05-03
构建固件和导入到JS
在 C++ 里加入以下代码, 然后重新编译构建固件:
// 为了控制固件的尺寸,BeShell 的 module 是按需引入的。
beshell.use<be::FS>() ;在JS中导入 fs module:
import * as fs from 'fs'import fs简介
FS 文件系统模块
提供文件系统操作功能,支持在 Flash 分区和 SD 卡上创建和使用文件系统。
支持的文件系统格式
BeShell 支持多种文件系统格式:
| 文件系统 | 适用场景 | 特点 |
|---|---|---|
| LittleFS | Flash 分区 | 推荐。专为嵌入式设备设计,具有断电安全保护、磨损均衡 |
| FATFS | Flash 分区 / SD 卡 | 兼容性好,适合与 PC 交换数据 |
| SPIFFS | Flash 分区 | 旧版支持,不推荐新项目使用 |
嵌入式设备推荐使用 LittleFS
- 断电安全:LittleFS 具有断电安全保护机制,写操作过程中断电不会损坏文件系统
- 磨损均衡:自动均衡 Flash 各区块的擦写次数,延长 Flash 寿命
- 小文件优化:特别适合存储配置文件、脚本等小文件
- 无需格式化:首次挂载时自动格式化,简化部署流程
FATFS 适用场景
- SD 卡文件系统(与 PC 交换数据)
- 需要与电脑直接读取存储内容的场景
使用示例
基本文件操作
import * as fs from "fs"
// 写入文件(根目录 / 对应 Flash 文件系统)
fs.writeFileSync("/config.json", '{"wifi": {"ssid": "mywifi"}}')
// 读取文件
const data = fs.readFileSync("/config.json")
const text = new TextDecoder().decode(data)
console.log(text)
// 创建目录
fs.mkdirSync("/data", true) // true 表示递归创建
// 列出根目录内容
const files = fs.listDirSync("/")
console.log(files)
// 检查文件是否存在
if (fs.existsSync("/config.json")) {
console.log("文件存在")
}
// 获取文件信息
const stat = fs.statSync("/config.json")
console.log("大小:", stat.size, "字节")
console.log("是目录:", stat.isDir)
// 删除文件
fs.unlinkSync("/temp.txt")
// 删除目录(递归删除)
fs.rmSync("/data", true)与 SD 卡配合使用
import * as fs from "fs"
import { SDCard } from "sdcard"
import * as serial from "serial"
// 初始化 SD 卡
const spi = serial.spi2
spi.setup({ mosi: 23, miso: 19, sck: 18 })
const sd = new SDCard()
sd.setup({
spi: 2,
cs: 13,
mount: "/sdcard"
})
// 现在可以访问 SD 卡上的文件
fs.writeFileSync("/sdcard/log.txt", "System started\n")
// 获取 SD 卡使用情况
const usage = fs.usage("/sdcard")
console.log("总容量:", usage.total)
console.log("已使用:", usage.used)
console.log("可用:", usage.free)文件流操作(大文件)
import * as fs from "fs"
// 打开文件进行写入
const handle = fs.open("/large-file.bin", "w")
// 分块写入数据
for (let i = 0; i < 100; i++) {
const buffer = new Uint8Array(1024)
// 填充数据...
fs.write(handle, buffer)
}
// 同步到存储介质(确保数据写入)
fs.sync(handle)
// 关闭文件
fs.close(handle)
// 读取大文件
const readHandle = fs.open("/large-file.bin", "r")
const chunk = fs.read(readHandle, 1024) // 读取 1KB
fs.close(readHandle)注意事项
- Flash 寿命:Flash 有擦写次数限制,避免频繁写入小文件
- 断电保护:虽然 LittleFS 有断电保护,但重要数据写入后建议调用
fs.sync() - 路径长度:嵌入式系统路径长度有限,建议使用简短路径
- 文件句柄:使用
open()打开的文件必须调用close()关闭,避免资源泄漏
模块函数
函数 mkdirSync
原型: mkdirSync (path:string, recursive:bool=false)
同步创建目录。
创建指定路径的目录。如果父目录不存在且 recursive 为 false,则创建失败。 当 recursive 为 true 时,会自动创建所有不存在的父目录(类似 mkdir -p)。
示例:
import * as fs from "fs"
// 创建单个目录(父目录必须存在)
fs.mkdirSync("/data")
// 递归创建目录(自动创建所有不存在的父目录)
fs.mkdirSync("/data/2024/01", true)参数:
path
类型string
参数说明要创建的目录路径
recursive
类型bool
默认值false
参数说明是否递归创建父目录
返回值:
类型bool
说明创建成功返回 true,失败返回 false
函数 rmdirSync
原型: rmdirSync (path:string, recursive:bool=false)
同步删除目录。
默认情况下,只能删除空目录。如果目录非空,返回 false。 当 recursive 参数为 true 时,可以递归删除目录及其所有内容。
示例:
import * as fs from "fs"
// 删除空目录
fs.rmdirSync("/empty-dir")
// 递归删除目录及其所有内容(谨慎使用!)
fs.rmdirSync("/data", true)参数:
path
类型string
参数说明要删除的目录路径
recursive
类型bool
默认值false
参数说明是否递归删除非空目录
返回值:
类型bool
说明删除成功返回 true,失败返回 false
函数 unlinkSync
原型: unlinkSync (path:string)
同步删除文件。
删除指定路径的文件。只能删除文件,不能删除目录(删除目录请使用 rmdirSync 或 rmSync)。 如果文件不存在或是目录,返回 false。
示例:
import * as fs from "fs"
// 删除单个文件
fs.unlinkSync("/temp.txt")
// 删除子目录下的文件
fs.unlinkSync("/config.old.json")
// 安全删除(先检查是否存在)
if (fs.existsSync("/temp.txt")) {
fs.unlinkSync("/temp.txt")
}参数:
path
类型string
参数说明要删除的文件路径
返回值:
类型bool
说明删除成功返回 true,失败返回 false
函数 readFileSync
原型: readFileSync (path:string, readlen:number=-1, offset:number=0)
同步读取整个文件内容。
读取指定文件的内容,返回 ArrayBuffer 对象。 可以使用 ArrayBuffer 的方法转换为字符串或其他格式。
// 函数返回的是一个 ArrayBuffer 对象,可以使用 TextDecoder 或自定义方法转换为字符串,例如:
let content = new TextDecoder().decode(fs.readFileSync("/foo.bar"))
console.log(content)
// 或者
content = fs.readFileSync("/foo.bar").asString()
console.log(content)参数:
path
类型string
参数说明要读取的文件路径
readlen
类型number
默认值-1
参数说明读取长度(字节),-1 表示读取到文件末尾
offset
类型number
默认值0
参数说明开始读取的位置偏移(字节)
异常:
- 路径是目录
- 文件不存在
- 打开文件失败
- 内存分配失败
返回值:
类型ArrayBuffer
说明文件内容
函数 writeFileSync
原型: writeFileSync (path:string, data:string|ArrayBuffer, append:bool=false)
同步写入数据到文件。
将数据写入指定文件。如果文件不存在则创建,存在则覆盖(除非 append 为 true)。 如果路径是目录,将抛出异常。
参数:
path
类型string
参数说明目标文件路径
data
类型string, ArrayBuffer
参数说明要写入的数据内容
append
类型bool
默认值false
参数说明是否在文件末尾追加写入,false 表示覆盖原有内容
异常:
- 路径是目录
- 打开文件失败
- 数据类型无效或为空
返回值:
类型number
说明实际写入的字节数
函数 listDirSync
原型: listDirSync (path:string, detail:bool=false)
同步读取目录下的所有成员。
获取指定目录下的文件和子目录列表。
如果 detail 参数为 false,返回文件名字符串数组。
如果 detail 参数为 true,返回包含详细信息的对象数组,格式如下:
[
{
name: string, // 文件名
type: "file"|"dir"|"unknown", // 类型:文件、目录或未知
size: number // 文件大小(字节),仅对文件有效
} ,
...
]示例:
import * as fs from "fs"
// 简单列出文件名
const files = fs.listDirSync("/")
console.log("文件列表:", files)
// 获取详细信息
const details = fs.listDirSync("/", true)
details.forEach(item => {
if (item.type === "file") {
console.log(`文件: ${item.name}, 大小: ${item.size} 字节`)
} else if (item.type === "dir") {
console.log(`目录: ${item.name}`)
}
})
// 遍历并筛选特定类型的文件
const allFiles = fs.listDirSync("/", true)
const jsonFiles = allFiles.filter(item =>
item.type === "file" && item.name.endsWith(".json")
)
console.log("JSON 文件:", jsonFiles)
// 计算目录总大小
let totalSize = 0
const items = fs.listDirSync("/data", true)
items.forEach(item => {
if (item.type === "file") {
totalSize += item.size
}
})
console.log("总大小:", totalSize, "字节")参数:
path
类型string
参数说明要读取的目录路径
detail
类型bool
默认值false
参数说明是否返回详细信息,false 只返回文件名数组
异常:
- 目录不存在
- 目录无法打开
- 权限不足
返回值:
类型string[], object[] 文件名数组或详细信息对象数组
函数 rmSync
原型: rmSync (path:string, recursive:bool=false)
同步删除文件或目录。
与 unlinkSync 不同,rmSync 可以删除目录(当 recursive 为 true 时)。 默认情况下,只能删除文件和空目录。
参数:
path
类型string
参数说明要删除的文件或目录路径
recursive
类型bool
默认值false
参数说明是否递归删除目录及其内容
返回值:
类型bool
说明删除成功返回 true,失败返回 false
函数 renameSync
原型: renameSync (oldPath:string, newPath:string)
同步重命名(移动)文件或目录。
将文件或目录从 oldPath 移动到 newPath。如果 newPath 已存在, 其行为取决于具体文件系统实现。
示例:
import * as fs from "fs"
// 重命名文件
fs.renameSync("/config.json", "/config.backup.json")
// 移动文件到另一个目录
fs.renameSync("/temp.txt", "/archive/temp.txt")
// 移动并重命名
fs.renameSync("/data.txt", "/backup/data-2024.txt")
// 重命名目录
fs.renameSync("/old-name", "/new-name")参数:
oldPath
类型string
参数说明原文件或目录路径
newPath
类型string
参数说明新文件或目录路径
返回值:
类型number
说明操作成功返回 0,失败返回非零错误代码(通常为 errno)
函数 statSync
原型: statSync (path:string)
同步获取文件或目录的状态信息。
返回指定路径的详细元数据信息。如果文件或目录不存在,返回 null。
示例:
import * as fs from "fs"
// 获取文件信息
const stat = fs.statSync("/config.json")
if (stat) {
console.log("大小:", stat.size, "字节")
console.log("是目录:", stat.isDir)
console.log("修改时间:", new Date(stat.mtime * 1000))
}
// 检查路径是否存在
if (fs.statSync("/data.txt")) {
console.log("文件存在")
} else {
console.log("文件不存在")
}
// 获取目录信息
const dirStat = fs.statSync("/data")
if (dirStat && dirStat.isDir) {
console.log("这是一个目录")
}参数:
path
类型string
参数说明要查询的文件或目录路径
返回值:
类型object, null
说明状态信息对象,路径不存在时返回 null
函数 existsSync
原型: existsSync (path:string)
同步检查路径是否存在。
检查指定路径的文件或目录是否存在。
示例:
import * as fs from "fs"
// 检查文件是否存在
if (fs.existsSync("/config.json")) {
console.log("配置文件存在")
}
// 检查目录是否存在
if (fs.existsSync("/data")) {
console.log("数据目录存在")
}
// 安全写入(避免覆盖)
if (!fs.existsSync("/important.txt")) {
fs.writeFileSync("/important.txt", "初始内容")
}参数:
path
类型string
参数说明要检查的路径
返回值:
类型bool
说明存在返回 true,不存在返回 false
函数 isFileSync
原型: isFileSync (path:string)
同步检查路径是否是文件。
如果路径存在且是常规文件,返回 true; 如果路径不存在或是目录等其他类型,返回 false。
示例:
import * as fs from "fs"
// 检查是否是文件
if (fs.isFileSync("/config.json")) {
console.log("这是一个文件")
}
// 遍历目录时筛选文件
const files = fs.listDirSync("/")
files.forEach(name => {
const path = `/${name}`
if (fs.isFileSync(path)) {
console.log("文件:", name)
}
})参数:
path
类型string
参数说明要检查的路径
返回值:
类型bool
说明是文件返回 true,否则返回 false
函数 isDirSync
原型: isDirSync (path:string)
同步检查路径是否是目录。
如果路径存在且是目录,返回 true; 如果路径不存在或是文件等其他类型,返回 false。
示例:
import * as fs from "fs"
// 检查是否是目录
if (fs.isDirSync("/data")) {
console.log("这是一个目录")
}
// 遍历目录时筛选子目录
const files = fs.listDirSync("/")
files.forEach(name => {
const path = `/${name}`
if (fs.isDirSync(path)) {
console.log("目录:", name)
}
})
// 确保目录存在(不存在则创建)
if (!fs.isDirSync("/logs")) {
fs.mkdirSync("/logs", true)
}参数:
path
类型string
参数说明要检查的路径
返回值:
类型bool
说明是目录返回 true,否则返回 false
函数 usage
原型: usage (path:string)
获取文件分区的存储使用情况。
返回指定挂载点的存储空间信息,包括总容量、已用空间和剩余空间。
返回对象格式:
{
total:number, // 分区总容量(字节)
used:number, // 已使用空间(字节)
free:number // 剩余可用空间(字节)
}示例:
import * as fs from "fs"
// 获取 Flash 分区使用情况
const fsUsage = fs.usage("/")
console.log("Flash 总容量:", fsUsage.total, "字节")
console.log("已使用:", fsUsage.used, "字节")
console.log("可用:", fsUsage.free, "字节")
console.log("使用率:", (fsUsage.used / fsUsage.total * 100).toFixed(2) + "%")
// 获取 SD 卡使用情况
const sdUsage = fs.usage("/sdcard")
console.log("SD 卡总容量:", (sdUsage.total / 1024 / 1024).toFixed(2), "MB")
console.log("SD 卡可用:", (sdUsage.free / 1024 / 1024).toFixed(2), "MB")
// 检查空间是否充足
if (fsUsage.free < 1024 * 1024) {
console.log("警告:Flash 空间不足 1MB")
}参数:
path
类型string
参数说明分区挂载点路径(如 "/fs"、"/sdcard" 等)
异常:
- 路径不是有效的分区挂载点
- 获取分区使用情况失败
返回值:
类型object (详见下方类型定义)
说明包含存储使用信息的对象
返回值类型定义:
{
total:number, // 总容量(字节)
used:number, // 已使用空间(字节)
free:number // 可用空间(字节)
}函数 open
原型: open (path:string, mode:string="rw")
打开文件,返回文件句柄用于后续操作。
mode 参数支持标准 C 库 fopen 的模式字符串:
"r"- 只读模式,文件必须存在"w"- 只写模式,文件不存在则创建,存在则清空"a"- 追加模式,写入到文件末尾"r+"- 读写模式,文件必须存在"w+"- 读写模式,文件不存在则创建,存在则清空"a+"- 读写追加模式
如果文件无法打开或创建,将抛出异常。
示例:
import * as fs from "fs"
// 只读模式打开文件
const readHandle = fs.open("/config.json", "r")
const data = fs.read(readHandle, 1024)
fs.close(readHandle)
// 写入模式打开文件(会清空原有内容)
const writeHandle = fs.open("/output.txt", "w")
fs.write(writeHandle, "Hello World")
fs.close(writeHandle)
// 追加模式打开文件
const appendHandle = fs.open("/log.txt", "a")
fs.write(appendHandle, "New log entry\n")
fs.close(appendHandle)
// 读写模式打开文件
const rwHandle = fs.open("/data.bin", "r+")
// 读取数据
const chunk = fs.read(rwHandle, 100)
// 移动指针到开头
fs.seek(rwHandle, 0, 0)
// 写入新数据
fs.write(rwHandle, new Uint8Array([1, 2, 3, 4]))
fs.close(rwHandle)参数:
path
类型string
参数说明文件路径
mode
类型string
默认值"rw"
参数说明打开模式(注意:默认 "rw" 不是标准模式,建议使用 "r+" 或 "w+")
异常:
- 文件不存在且模式为只读
- 权限不足
- 路径是目录
- 打开文件失败
返回值:
类型number
说明文件句柄(文件描述符)
函数 read
原型: read (handle:number, length:number)
从已打开的文件中读取指定长度的数据。
从当前文件指针位置开始读取,读取后文件指针会自动后移。 实际读取的字节数可能小于请求的长度(如到达文件末尾)。
import {open,read,close} from "fs"
const handle = open("/sdcard/test.txt", "r") ;
const data = read(handle, 10) ;
console.log("readed bytes:", data.byteLength) ;
console.log("content:", data.toString()) ;
close(handle) ;参数:
handle
类型number
参数说明由 open() 返回的文件句柄
length
类型number
参数说明要读取的字节数
异常:
- 参数数量不足
- 文件句柄无效
- 内存分配失败
返回值:
类型ArrayBuffer
说明读取到的数据,长度可能小于请求值
函数 write
原型: write (handle:number, data:ArrayBuffer|string)
向已打开的文件写入数据。
从当前文件指针位置开始写入,写入后文件指针会自动后移。 支持写入字符串或 ArrayBuffer 类型的数据。
import {open,write,close} from "fs"
const handle = open("/sdcard/test.txt", "w") ;
let wrote = write(handle, "hello world") ;
console.log("wrote bytes:", wrote) ;
close(handle) ;参数:
handle
类型number
参数说明由 open() 返回的文件句柄
data
类型ArrayBuffer, string
参数说明要写入的数据
返回值:
类型number
说明实际写入的字节数
函数 seek
原型: seek (handle:number, offset:number, whence:number=0)
移动文件指针到指定位置。
设置文件读写的当前位置,影响后续的 read/write 操作。
whence 参数说明:
0(SEEK_SET) - 从文件开头计算偏移量1(SEEK_CUR) - 从当前位置计算偏移量2(SEEK_END) - 从文件末尾计算偏移量
参数:
handle
类型number
参数说明由 open() 返回的文件句柄
offset
类型number
参数说明偏移量(字节)
whence
类型number
默认值0
参数说明偏移基准位置:0=文件头, 1=当前位置, 2=文件尾
返回值:
类型number
说明返回 0 表示成功,非零值表示错误代码
函数 tell
原型: tell (handle:number)
返回文件当前的读写位置。
获取文件指针的当前偏移量(相对于文件开头的字节数)。
参数:
handle
类型number
参数说明由 open() 返回的文件句柄
返回值:
类型number
说明当前文件指针位置(字节偏移量)
函数 flush
原型: flush (handle:number)
刷新文件缓冲区,将缓冲数据写入操作系统。
fflush() 将用户空间缓冲区数据写入内核,但不保证数据已写入物理存储设备。 如需确保数据落盘,请使用 sync() 函数。
注意:在 esp32 平台上效果可能受限。
参数:
handle
类型number
参数说明由 open() 返回的文件句柄
返回值:
类型number
说明返回 0 表示成功,非零值(通常为 EOF/-1)表示失败
函数 sync
原型: sync (handle:number)
将文件数据同步写入物理存储介质。
fsync() 确保所有缓冲数据(包括文件元数据)已写入物理设备。 这是一个相对耗时的操作,频繁调用可能影响性能。
注意:在 esp32 平台上效果可能受限。
参数:
handle
类型number
参数说明由 open() 返回的文件句柄
返回值:
类型number
说明返回 0 表示成功,非零值表示错误代码
函数 close
原型: close (handle:number)
关闭 open 打开的文件句柄。
关闭文件,释放相关资源。关闭后该句柄不可再使用。 建议在完成文件操作后及时关闭,避免资源泄漏。
参数:
handle
类型number
参数说明由 open() 返回的文件句柄
返回值:
类型number
说明返回 0 表示成功,非零值(通常为 EOF/-1)表示失败
