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);
}