使用 Node.js 很大一部分的使用场景是,写个小脚本批量处理一下重复无聊的任务。比如说:
- 获取所有 json 内容,然后过滤出想要的数据。
- 把所有图片压缩一下,然后放入压缩包中。
- 把字体文件提取压缩一下。
- 把所有文件重命名一下。
这些任务中我们都需要访问文件系统,之前在浏览器环境中其实是没有文件系统概念的,今天我们来学一哈。
Node.js 文件模块
我们可以使用 fs
来操作文件系统。
const fs = require('fs');
回调方式
所有的文件系统操作都具有同步的(10.x 有,历史也有)、回调的(10.x 有,历史也有)、以及基于 promise(12.x 开始就有了,历史没有) 的形式。
同步
同步的形式会阻止 Node.js 事件循环和进一步的 JavaScript 执行,直到操作完成。
异常会被立即地抛出,可以使用 try…catch
处理,也可以冒泡。
const fs = require('fs'); try { fs.unlinkSync('文件'); console.log('已成功地删除文件'); } catch (err) { // 处理错误 }
异步
异步的形式总是把完成回调作为其最后一个参数。
传给完成回调的参数取决于具体方法,但第一个参数总是预留给异常。 如果操作被成功地完成,则第一个参数会为 null
或 und
efined
。
const fs = require('fs'); fs.unlink('文件', (err) => { if (err) throw err; console.log('已成功地删除文件'); });
promise
基于 Promise 的操作会返回 Promise
(当异步操作完成时会被解决)。
const fs = require('fs/promises'); (async function(path) { try { await fs.unlink(path); console.log(`已成功地删除文件 ${path}`); } catch (error) { console.error('出错:', error.message); } })('文件');
文件路径
支持 字符串(常用Path库就对了)、Buffer(为了兼容)、URL(需要 new URL('file:///C:/'
) 为入参。可以用相对路径 (process.cwd()
可以查看当前路径),绝对路径。
字符串形式
在 Windows 上,Node.js 遵循独立驱动器工作目录的概念。 当使用没有反斜杠的驱动器路径时,可以观察到此行为。 例如, fs.readdirSync('C:\\')
可能会返回与 fs.readdirSync('C:')
不同的结果。 详见此 MSDN 页面。
一般我们使用 Path 库:
- 可以直接看一下最终地址
- 可以正确使用当前系统的路径分隔符,Unix系统是"/",Windows系统是""。
URL 形式
// 在 Windows 上: // - 带有主机名的 WHATWG 文件的 URL 会转换为 UNC 路径。 // file://主机名/文件 => 主机名文件 fs.readFileSync(new URL('file://主机名/文件')); // - 带有驱动器号的 WHATWG 文件的 URL 会转换为绝对路径。 // file:///C:/文件 => C:文件 fs.readFileSync(new URL('file:///C:/文件'));
API
目录 dir 类
fs.readdirSync()
同步获取目录
fs.readdir()
异步获取目录
当调用 fs.readdir()
或 fs.readdirSync()
(withFileTypes
选项设置为 true
)时,则生成的数组会使用 fs.Dirent
对象(而不是字符串或 Buffer
)填充。
fs.mkdir(path[, options], callback)
异步地创建目录。
回调会传入可能的异常、以及创建的第一个目录的路径(如果 recursive
为 true
), (err, [path])
。可选的 options
参数可以是整数(指定 mode
(权限和粘滞位))、或对象(具有 mode
属性和 recursive
属性(指示是否要创建父目录))。
当 path
是已存在的目录时,调用 fs.mkdir()
仅在 recursive
为 false 时才会导致错误。
fs.mkdirSync(path[, options])
同步地创建目录。 返回 undefined
,或创建的第一个目录的路径(如果 recursive
为 true
)。 这是 fs.mkdir()
的同步版本。
监听 fs.FSWatcher 类
fs.watch()
每当指定监视的文件被修改时,会触发 'change'
事件。
// 使用 fs.watch()监听器的示例。 fs.watch('./tmp', { encoding: 'buffer' }, (eventType, filename) => { if (filename) { console.log(filename); } });
读取流 fs.ReadStream 类
使用 fs.createReadStream()
函数创建并返回的 fs.ReadStream
实例。
fs.createReadStream(path[, options])
flags
<string> 参见文件系统flag
的支持。 默认值:'r'
。encoding
<string> 默认值:null
。fd
<integer> 默认值:null
。mode
<integer> 默认值:0o666
。autoClose
<boolean> 默认值:true
。emitClose
<boolean> 默认值:false
。start
<integer>end
<integer> 默认值:Infinity
。highWaterMark
<integer> 默认值:64 * 1024
。fs
<Object> | <null> 默认值:null
。
- 返回: <fs.ReadStream> 参见可读流。
写入流 fs.WriteStream 类
使用 fs.createWriteStream()
函数创建并返回的 fs.WriteStream
实例。
fs.createWriteStream(path[, options])
flags
<string> 参见文件系统flag
的支持。 默认值:'w'
。encoding
<string> 默认值:'utf8'
。fd
<integer> 默认值:null
。mode
<integer> 默认值:0o666
。autoClose
<boolean> 默认值:true
。emitClose
<boolean> 默认值:false
。start
<integer>fs
<Object> | <null> 默认值:nul
l
。
- 返回: <fs.WriteStream> 参见可写流。
文件信息 fs.Stats 类
fs.Stats
对象提供了关于文件的信息。在遍历的时候我们会判断文件类型,如果是文件就打开,如果是目录就递归
从 fs.stat()
、fs.lstat()
、fs.fstat()
、以及它们的同步方法返回的对象都是此类型。 如果传给这些方法的 options
中的 bigint
为 true,则数值会是 bigint
型而不是 number
型,并且该对象还会包含额外的纳秒级精度的属性(以 Ns
为后缀)。
stats.isDirectory()
如果fs.Stats
对象描述文件系统目录,则返回true
。
stats.isFile()
如果fs.Stats
对象描述普通的文件,则返回true
。
stats.size
文件的大小(以字节为单位)。
stats.mtime
表明上次修改此文件的时间戳。stats.mtimeMs
表明上次修改此文件的时间戳,以 POSIX 纪元以来的毫秒数表示。
stats.birthtime
表示此文件的创建时间的时间戳。stats.birthtimeMs
表明此文件的创建时间的时间戳,以 POSIX 纪元以来的毫秒数表示。
文件属性的时间值#
atimeMs
、mtimeMs
、ctimeMs
和birthtimeMs
属性是保存相应时间(以毫秒为单位)的数值。 它们的精度取决于平台。 当将bigint: true
传给生成该对象的方法时,属性将会是 bigint 型,否则它们将会是数字型。
atimeNs
、mtimeNs
、ctimeNs
和birthtimeNs
属性是保存相应时间(以纳秒为单位)的 bigint。 仅当将bigint: true
传给生成该对象的方法时,它们才会出现。 它们的精度取决于平台。
atime
、mtime
、ctime
和birthtime
是对应时间的Date
对象。Date
值和数值没有关联性。 赋值新的数值、或者改变Date
的值,都将不会影响到对应的属性。
stat 对象中的时间具有以下语义:
ctime
"更改时间" - 上次更改文件状态(修改索引节点数据)的时间。由chmod(2)
、chown(2)
、link(2)
、mknod(2)
、rename(2)
、unlink(2)
、utimes(2)
、read(2)
和write(2)
系统调用更改。
birthtime
"创建时间" - 创建文件的时间。当创建文件时设置一次。 在不支持创建时间的文件系统上,该字段可能改为保存ctime
或1970-01-01T00:00Z
(即 Unix 纪元时间戳0
)。 在这种情况下,该值可能大于atime
或mtime
。 在 Darwin 和其他的 FreeBSD 衍生系统上,也可能使用utimes(2)
系统调用将atime
显式地设置为比birthtime
更早的值。
在 Node.js 0.12 之前,在 Windows 系统上
ctime
保存birthtime
。 从 0.12 开始,ctime
不再是“创建时间”,而在 Unix 系统上则从来都不是。
获取权限访问 fs.access、fs.accessSync
除了判断权限,还可以判断是否存在。不过我们一般可以直接 fs.open()
直接去处理 err 。通常,仅在不直接使用文件时(例如当其可访问性是来自其他进程的信号时),才检查文件的可访问性。
在 Windows 上,目录上的访问控制策略(ACL)可能会限制对文件或目录的访问。 但是, fs.access()
函数不检查 ACL,因此即使 ACL 限制用户读取或写入,也可能报告路径是可访问的。
fs.access(path[, mode], callback)
mode
<integer> 默认值:fs.constants.F_OK
。
callback
<Function>
err
<Error>
测试用户对 path
指定的文件或目录的权限。 mode
参数是一个可选的整数,指定要执行的可访问性检查。 查看文件可访问性的常量了解 mode
的可选值。 可以创建由两个或更多个值按位或组成的掩码(例如 fs.constants.W_OK | fs.constants.R_OK
)。
文件
fs.open(path[, flags[, mode]], callback)
异步地打开文件。fs.openSync(path[, flags, mode])
同步打开文件
fs.read(fd, buffer, offset, length, position, callback)
从fd
指定的文件中读取数据,写入到buffer
中。
fs.readSync(fd, buffer, offset, length, position)
同步版本,从指定 fs 中读取数据
fs.readFile(path[, options], callback)
异步地读取文件的全部内容。fs.readFileSync(path[, options])
同步版本,读取文件全部内容
fs.appendFile(path, data[, options], callback)
异步地追加数据到文件,如果文件尚不存在则创建文件。data
可以是字符串或Buffer
。
fs.appendFileSync(path, data[, options])
同步地将数据追加到文件,如果文件尚不存在则创建该文件。 data
可以是字符串或 Buffer
。
// 直接针对文件追加 fs.appendFileSync('文件.txt', '追加的数据', 'utf8'); // 先打开文件,然后针对文件描述符追加 fd = fs.openSync('文件.txt', 'a'); fs.appendFileSync(fd, '追加的数据', 'utf8');
fs.copyFile(src, dest[, mode], callback)
异步地将src
拷贝到dest
。 默认情况下,如果dest
已经存在,则覆盖它。 除了可能的异常,回调函数没有其他参数。 Node.js 不保证拷贝操作的原子性。 如果在打开目标文件用于写入后发生错误,则 Node.js 将尝试删除目标文件。
fs.copyFileSync(src, dest[, mode])
同步
fs.rename(oldPath, newPath, callback)
异步地把oldPath
文件重命名为newPath
提供的路径名。 如果newPath
已存在,则覆盖它。 除了可能的异常,完成回调没有其他参数。
fs.renameSync(oldPath, newPath)
同步版本,文件重命名
fs.write(fd, buffer[, offset[, length[, position]]], callback)
写入buffer
到fd
指定的文件。不等待回调就对同一个文件多次使用fs.write()
是不安全的。 对于这种情况,建议使用fs.createWriteStream()
。
示例
获取目录下所有图片,并上传到服务器
const FormData = require('form-data'); const fetch = require('node-fetch'); var fs = require('fs'); var path = require('path'); var dirPath = 'cdn-transform' var filePath = path.resolve(`./node-upload-img/${dirPath}`); //文件遍历方法 function fileDisplay(filePath){ fs.readdir(filePath,function(err,files){ if(err){ console.warn(err) }else{ files.forEach(function(filename){ var filedir = path.join(filePath, filename); var buffer = fs.readFileSync(filedir) var formData = new FormData(); var fileName = `${filename}` var url = `www.lilnong.top/upload/fe-up/${dirPath}/${fileName}`; formData.append('file', buffer, `${fileName}`); fetch('http://www.lilnong.top/upload',{ headers: formData.getHeaders(), method: 'post', body: formData }) .then(v=>v.text()) .then(v=>{ console.log('http://www.lilnong.top/upload', url, v) }) }) } }); } fileDisplay(filePath)
递归遍历所有json
const readDir = (entry, paths = []) => { const dirInfo = fs.readdirSync(entry); dirInfo.forEach(item=>{ const location = path.join(entry,item); const info = fs.statSync(location); if(info.isDirectory()){ console.log(`dir:${location}`); readDir(location, [item]); }else{ if(/.json$/.test(location)){ readFile(location, paths) } } }) } console.log('__dirname', __dirname) readDir(__dirname); function readFile(path, pathKey){ return console.log(path, pathKey); })