Skip to content
本页内容

fs (文件系统) ​

文件 I/O 是对标准 POSIX 函数的简单封装。 通过 require('fs') 使用该模块。 所有的方法都有异步和同步的形式。

异步方法的最后一个参数都是一个回调函数。 传给回调函数的参数取决于具体方法,但回调函数的第一个参数都会保留给异常。 如果操作成功完成,则第一个参数会是 null 或 undefined。

当使用同步方法时,任何异常都会被立即抛出。 可以使用 try/catch 来处理异常,或让异常向上冒泡。

异步方法的例子:

js
const fs = require("fs");

fs.unlink("/tmp/hello", (err) => {
    if (err) throw err;
    console.log("成功删除 /tmp/hello");
});

同步方法的例子:

js
const fs = require("fs");

fs.unlinkSync("/tmp/hello");
console.log("成功删除 /tmp/hello");

异步的方法不能保证执行顺序。 所以下面的例子可能会出错:

js
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 之前执行。 正确的方法是把回调链起来。

js
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 环境变量:

注意:不建议省略异步方法的回调函数,未来的版本可能会导致抛出错误。

txt
$ 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 路径文档。

注意: 在 Windows 上,使用 w 选项(通过 fs.open 或 fs.writeFile) 打开已有隐藏文件将会失败,错误信息为 EPERM 。已有隐藏文件可以通过 r+ 选项打开。调用 fs.ftruncate 可以用来重置文件内容。

创建文件夹 ​

fs.mkdir ​

js
fs.mkdir(path[, mode], callback)

异步地创建目录。 完成回调只有一个可能的异常参数。 mode 默认为 0o777

fs.mkdirSync ​

js
fs.mkdirSync(path[, mode])

同步地创建目录。 返回 undefined。

fs.mkdtemp ​

js
fs.mkdtemp(prefix[, options], callback)

创建一个唯一的临时目录。

生成六位随机字符附加到一个要求的 prefix 后面,然后创建一个唯一的临时目录。

创建的目录路径会作为字符串传给回调的第二个参数。

可选的 options 参数可以是一个字符串并指定一个字符编码,或是一个对象且由一个 encoding 属性指定使用的字符编码。

例子:

js
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)结尾。

js
// 新建的临时目录的父目录
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 ​

js
const content = fs.readFileSync("./tt.txt");
console.log(content.toString());

fs.readdirSync ​

js
fs.readdirSync(path[, options])

fs.read ​

js
fs.read(fd, buffer, offset, length, position, callback);

从 fd 指定的文件中读取数据。

buffer 是数据将被写入到的 buffer。

offset 是 buffer 中开始写入的偏移量。

length 是一个整数,指定要读取的字节数。

position 指定从文件中开始读取的位置。 如果 position 为 null,则数据从当前文件读取位置开始读取,且文件读取位置会被更新。 如果 position 为一个整数,则文件读取位置保持不变。

回调有三个参数 (err, bytesRead, buffer)。

fs.stat ​

js
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 ​

js
fs.writeFile(file, data[, options], callback)

异步地写入数据到文件,如果文件已经存在,则替代文件。 data 可以是一个字符串或一个 buffer。

如果 data 是一个 buffer,则忽略 encoding 选项。它默认为 'utf8'。

例子:

js
fs.writeFile("message.txt", "Hello Node.js", (err) => {
    if (err) throw err;
    console.log("The file has been saved!");
});

如果 options 是一个字符串,则它指定了字符编码。例如:

js
fs.writeFile("message.txt", "Hello Node.js", "utf8", callback);

任何指定的文件描述符必须支持写入。

注意,多次对同一文件使用 fs.writeFile 且不等待回调,是不安全的。 对于这种情况,强烈推荐使用 fs.createWriteStream。

注意:如果 file 指定为一个文件描述符,则它不会被自动关闭。

fs.writeFileSync ​

js
fs.writeFileSync(file, data[, options])

fs.writeFile('tt.txt', '蒹葭苍苍,白露为霜。', (err) => {
    if (err) throw err;
    console.log('写入成功');
})

fs.writeFile() 的同步版本。返回 undefined。

fs.appendFile ​

js
fs.appendFile("tt.txt", "\r\n所谓伊人,在水一方。", (err) => {
    if (err) throw err;
    console.log("追加内容成功");
});

删除文件 ​

异步删除文件

fs.unlinkSync(path) ​

同步删除文件

fs.rmdirSync(path) ​

同步删除文件夹。

不能删除非空文件夹。

文件监听 ​

fs.watch ​

js
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 ​

js
fs.watchFile(filename[, options], listener)

监视 filename 的变化。 回调 listener 会在每次访问文件时被调用。

options 参数可被省略。 如果提供的话,它应该是一个对象。 options 对象可能包含一个名为 persistent 的布尔值,表明当文件正在被监视时,进程是否应该继续运行。 options 对象可以指定一个 interval 属性,表示目标应该每隔多少毫秒被轮询。 默认值为 { persistent: true, interval: 5007 }。

listener 有两个参数,当前的状态对象和以前的状态对象:

js
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 (消失)相同。

这种情况会发生在:

  • 该文件被删除,然后又恢复
  • 文件重命名两次 - 第二次重命名与其原名称相同

实践应用 ​

递归删除文件 ​

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

批量重命名文件 ​

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