使用 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 或 undefined。
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> 默认值:null。
- 返回: <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); })


