FileSystem
# fs (文件系统)
文件 I/O 是对标准 POSIX 函数的简单封装。 通过 require('fs') 使用该模块。 所有的方法都有异步和同步的形式。
异步方法的最后一个参数都是一个回调函数。 传给回调函数的参数取决于具体方法,但回调函数的第一个参数都会保留给异常。 如果操作成功完成,则第一个参数会是 null 或 undefined。
当使用同步方法时,任何异常都会被立即抛出。 可以使用 try/catch 来处理异常,或让异常向上冒泡。
异步方法的例子:
const fs = require('fs');
fs.unlink('/tmp/hello', (err) => {
if (err) throw err;
console.log('成功删除 /tmp/hello');
});
同步方法的例子:
const fs = require('fs');
fs.unlinkSync('/tmp/hello');
console.log('成功删除 /tmp/hello');
异步的方法不能保证执行顺序。 所以下面的例子可能会出错:
fs.rename('/tmp/hello', '/tmp/world', (err) => {
if (err) throw err;
console.log('重命名完成');
});
fs.stat('/tmp/world', (err, stats) => {
if (err) throw err;
console.log(`文件属性: ${JSON.stringify(stats)}`);
});
fs.stat 可能在 fs.rename 之前执行。 正确的方法是把回调链起来。
fs.rename('/tmp/hello', '/tmp/world', (err) => {
if (err) throw err;
fs.stat('/tmp/world', (err, stats) => {
if (err) throw err;
console.log(`文件属性: ${JSON.stringify(stats)}`);
});
});
在繁忙的进程中,建议使用异步的方法。 同步的方法会阻塞整个进程,直到完成(停止所有连接)。
可以使用文件名的相对路径。 路径是相对 process.cwd() 的。
大多数 fs 函数可以省略回调函数,在这种情况下,会使用默认的回调函数。 若要追踪最初的调用点,可设置 NODE_DEBUG 环境变量:
注意:不建议省略异步方法的回调函数,未来的版本可能会导致抛出错误。
$ cat script.js
function bad() {
require('fs').readFile('/');
}
bad();
$ env NODE_DEBUG=fs node script.js
fs.js:88
throw backtrace;
^
Error: EISDIR: illegal operation on a directory, read
<stack trace.>
注意:在 Windows 上 Node.js 遵循单驱动器工作目录的理念。 当使用驱动器路径且不带反斜杠时就能体验到该特征。 例如,fs.readdirSync('c:\\') 可能返回与 fs.readdirSync('c:') 不同的结果。 详见 MSDN 路径文档 (opens new window)。
注意: 在 Windows 上,使用 w 选项(通过 fs.open 或 fs.writeFile) 打开已有隐藏文件将会失败,错误信息为 EPERM 。已有隐藏文件可以通过 r+ 选项打开。调用 fs.ftruncate 可以用来重置文件内容。
# 创建文件夹
# fs.mkdir
fs.mkdir(path[, mode], callback)
异步地创建目录。 完成回调只有一个可能的异常参数。 mode 默认为 0o777
# fs.mkdirSync
fs.mkdirSync(path[, mode])
同步地创建目录。 返回 undefined。
# fs.mkdtemp
fs.mkdtemp(prefix[, options], callback)
创建一个唯一的临时目录。
生成六位随机字符附加到一个要求的 prefix 后面,然后创建一个唯一的临时目录。
创建的目录路径会作为字符串传给回调的第二个参数。
可选的 options 参数可以是一个字符串并指定一个字符编码,或是一个对象且由一个 encoding 属性指定使用的字符编码。
例子:
fs.mkdtemp(path.join(os.tmpdir(), 'foo-'), (err, folder) => {
if (err) throw err;
console.log(folder);
// 输出: /tmp/foo-itXde2 or C:\Users\...\AppData\Local\Temp\foo-itXde2
});
注意:fs.mkdtemp() 方法会直接附加六位随机选择的字符串到 prefix 字符串。 例如,指定一个目录 /tmp,如果目的是要在 /tmp 里创建一个临时目录,则 prefix 必须 以一个指定平台的路径分隔符(require('path').sep)结尾。
// 新建的临时目录的父目录
const tmpDir = os.tmpdir();
// 该方法是 *错误的*:
fs.mkdtemp(tmpDir, (err, folder) => {
if (err) throw err;
console.log(folder);
// 会输出类似于 `/tmpabc123`。
// 注意,一个新的临时目录会被创建在文件系统的根目录,而不是在 /tmp 目录里。
});
// 该方法是 *正确的*:
const { sep } = require('path');
fs.mkdtemp(`${tmpDir}${sep}`, (err, folder) => {
if (err) throw err;
console.log(folder);
// 会输出类似于 `/tmp/abc123`。
// 一个新的临时目录会被创建在 /tmp 目录里。
});
# 读取文件
# fs.readFileSync
const content = fs.readFileSync('./tt.txt')
console.log(content.toString());
# fs.readdirSync
fs.readdirSync(path[, options])
# fs.read
fs.read(fd, buffer, offset, length, position, callback)
从 fd 指定的文件中读取数据。
buffer 是数据将被写入到的 buffer。
offset 是 buffer 中开始写入的偏移量。
length 是一个整数,指定要读取的字节数。
position 指定从文件中开始读取的位置。 如果 position 为 null,则数据从当前文件读取位置开始读取,且文件读取位置会被更新。 如果 position 为一个整数,则文件读取位置保持不变。
回调有三个参数 (err, bytesRead, buffer)。
# fs.stat
fs.stat(path, callback)
fs.statSync(path)
回调有两个参数 (err, stats) 其中 stats 是一个 fs.Stats 对象。
如果发生错误,则 err.code 会是常见系统错误之一。
不建议在调用 fs.open() 、fs.readFile() 或 fs.writeFile() 之前使用 fs.stat() 检查一个文件是否存在。 作为替代,用户代码应该直接打开/读取/写入文件,当文件无效时再处理错误。
如果要检查一个文件是否存在且不操作它,推荐使用 fs.access()。
# fs.Stats 对象
stats.isFile()stats.isDirectory()stats.isBlockDevice()stats.isCharacterDevice()stats.isSymbolicLink()(仅对fs.lstat()有效)stats.isFIFO()stats.isSocket()
# fs.existsSync(path)
如果路径存在,则返回 true,否则返回 false。
# 写入文件
# fs.writeFile
fs.writeFile(file, data[, options], callback)
异步地写入数据到文件,如果文件已经存在,则替代文件。 data 可以是一个字符串或一个 buffer。
如果 data 是一个 buffer,则忽略 encoding 选项。它默认为 'utf8'。
例子:
fs.writeFile('message.txt', 'Hello Node.js', (err) => {
if (err) throw err;
console.log('The file has been saved!');
});
如果 options 是一个字符串,则它指定了字符编码。例如:
fs.writeFile('message.txt', 'Hello Node.js', 'utf8', callback);
任何指定的文件描述符必须支持写入。
注意,多次对同一文件使用 fs.writeFile 且不等待回调,是不安全的。 对于这种情况,强烈推荐使用 fs.createWriteStream。
注意:如果 file 指定为一个文件描述符,则它不会被自动关闭。
# fs.writeFileSync
fs.writeFileSync(file, data[, options])
fs.writeFile('tt.txt', '蒹葭苍苍,白露为霜。', (err) => {
if (err) throw err;
console.log('写入成功');
})
fs.writeFile() 的同步版本。返回 undefined。
# fs.appendFile
fs.appendFile('tt.txt', '\r\n所谓伊人,在水一方。', (err) => {
if (err) throw err;
console.log('追加内容成功');
})
# 删除文件
# fs.unlink(path, callback)
异步删除文件
# fs.unlinkSync(path)
同步删除文件
# fs.rmdirSync(path)
同步删除文件夹。
不能删除非空文件夹。
# 文件监听
# fs.watch
fs.watch(filename[, options][, listener])
fs.watch('./3/', (eventType, fileName) => {
console.log(eventType, fileName);
})
监视 filename 的变化,filename 可以是一个文件或一个目录。 返回的对象是一个 fs.FSWatcher。
第二个参数是可选的。 如果提供的 options 是一个字符串,则它指定了 encoding。 否则 options 应该以一个对象传入。
监听器回调有两个参数 (eventType, filename)。 eventType 可以是 'rename' 或 'change',filename 是触发事件的文件的名称。
注意,在大多数平台,当一个文件出现或消失在一个目录里时,'rename' 会被触发。
还需要注意,监听器回调是绑定在由 fs.FSWatcher 触发的 'change' 事件上,但它跟 eventType 的 'change' 值不是同一个东西。
说明
fs.watch API 不是 100% 跨平台一致的,且在某些情况下不可用。
递归选项只支持 macOS 和 Windows。
# fs.watchFile
fs.watchFile(filename[, options], listener)
监视 filename 的变化。 回调 listener 会在每次访问文件时被调用。
options 参数可被省略。 如果提供的话,它应该是一个对象。 options 对象可能包含一个名为 persistent 的布尔值,表明当文件正在被监视时,进程是否应该继续运行。 options 对象可以指定一个 interval 属性,表示目标应该每隔多少毫秒被轮询。 默认值为 { persistent: true, interval: 5007 }。
listener 有两个参数,当前的状态对象和以前的状态对象:
fs.watchFile('message.text', (curr, prev) => {
console.log(`the current mtime is: ${curr.mtime}`);
console.log(`the previous mtime was: ${prev.mtime}`);
});
这里的状态对象是 fs.Stat 实例。
如果你想在文件被修改而不只是访问时得到通知,则需要比较 curr.mtime 和 prev.mtime。
注意:当一个 fs.watchFile 的运行结果是一个 ENOENT 错误时,它会调用监听器一次,且将所有字段置零(或将日期设为 Unix 纪元)。 在 Windows 中,blksize 和 blocks 字段会是 undefined 而不是零。 如果文件是在那之后创建的,则监听器会被再次调用,且带上最新的状态对象。 这是在 v0.10 版之后在功能上的变化。
注意:fs.watch() 比 fs.watchFile 和 fs.unwatchFile 更高效。 可能的话,应该使用 fs.watch 而不是 fs.watchFile 和 fs.unwatchFile。
注意: 当 fs.watchFile() 所监听的文件消失并重新出现时,第二个回调函数中返回的 previousstat (文件重新出现)将与第一个回调函数的 previousstat (消失)相同。
这种情况会发生在:
- 该文件被删除,然后又恢复
- 文件重命名两次 - 第二次重命名与其原名称相同
# ——拉出来溜溜——
# 递归删除文件
function rmdir(dirPath) {
let files = fs.readdirSync(dirPath);
console.log(files);
files.forEach((file, index) => {
const filePath = path.join(dirPath, file);
if (fs.statSync(filePath).isDirectory()) {
rmdir(filePath);
} else {
fs.unlinkSync(filePath);
}
})
fs.rmdirSync(dirPath);
}
# 批量重命名文件
const fs = require('fs');
const path = require('path')
const dirName = "新建文件夹";
const reg = /^20200818.*\.jpg$/;
const fdirpath = path.join(__dirname, dirName);
// console.log(fdirpath);
/* 读取源文件夹下的所有文件,批量处理
* fs.existsSync(path) 以同步的方法检测目录是否存在,返回boolean
* fs.readdirSync(path) 方法将返回该路径下所有文件名的数组
* fs.renameSync(oldPath, newPath) 返回 undefined
*/
if (fs.existsSync(fdirpath)) {
let files = fs.readdirSync(fdirpath);
for (let i = 0; i < files.length; i++) {
const fileName = files[i];
if (reg.test(fileName)) {
const oldPath = path.join(fdirpath, fileName);
const newPath = path.join(fdirpath, `${dirName} (${i + 1}).jpg`);
// fs.renameSync(oldPath, newPath);
console.log("执行重命名", `${oldPath} -> ${newPath}`);
} else {
console.log("未执行重命名", fileName);
}
}
} else {
console.log("文件读取失败:" + fdirpath);
}