http://nodejs.cn/api/fs.html#fs_file_system

fs(文件系统)

fs 模块提供了一个 API,用于以模仿标准 POSIX 函数的方式与文件系统进行交互。
要使用此模块:

  1. const fs = require('fs');

所有文件系统操作都具有同步和异步的形式。
异步的形式总是将完成回调作为其最后一个参数。 传给完成回调的参数取决于具体方法,但第一个参数始终预留用于异常。 如果操作成功完成,则第一个参数将为 nullundefined

  1. const fs = require('fs');
  2. fs.unlink('/tmp/hello', (err) => {
  3. if (err) throw err;
  4. console.log('已成功删除 /tmp/hello');
  5. });

使用同步的操作发生的异常会立即抛出,可以使用 try/catch 处理,也可以允许冒泡。

  1. const fs = require('fs');
  2. try {
  3. fs.unlinkSync('/tmp/hello');
  4. console.log('已成功删除 /tmp/hello');
  5. } catch (err) {
  6. // 处理错误
  7. }

使用异步的方法时无法保证顺序。 因此,以下的操作容易出错,因为 fs.stat() 操作可能在 fs.rename() 操作之前完成:

  1. fs.rename('/tmp/hello', '/tmp/world', (err) => {
  2. if (err) throw err;
  3. console.log('重命名完成');
  4. });
  5. fs.stat('/tmp/world', (err, stats) => {
  6. if (err) throw err;
  7. console.log(`文件属性: ${JSON.stringify(stats)}`);
  8. });

要正确地排序这些操作,则将 fs.stat() 调用移动到 fs.rename() 操作的回调中:

  1. fs.rename('/tmp/hello', '/tmp/world', (err) => {
  2. if (err) throw err;
  3. fs.stat('/tmp/world', (err, stats) => {
  4. if (err) throw err;
  5. console.log(`文件属性: ${JSON.stringify(stats)}`);
  6. });
  7. });

在繁忙的进程中,强烈建议使用这些调用的异步版本。 同步的版本将阻塞整个进程,直到它们完成(停止所有连接)。
虽然不推荐这样使用,但大多数 fs 函数允许省略回调参数,在这种情况下,使用一个会重新抛出错误的默认回调。 要获取原始调用点的跟踪,则设置 NODE_DEBUG 环境变量:
不推荐在异步的 fs 函数上省略回调函数,因为可能导致将来抛出错误。

  1. $ cat script.js
  2. function bad() {
  3. require('fs').readFile('/');
  4. }
  5. bad();
  6. $ env NODE_DEBUG=fs node script.js
  7. fs.js:88
  8. throw backtrace;
  9. ^
  10. Error: EISDIR: illegal operation on a directory, read
  11. <stack trace.>

文件路径

大多数 fs 操作接受的文件路径可以指定为字符串、[Buffer](http://nodejs.cn/s/FApxjh)、或使用 file: 协议的 [URL](http://nodejs.cn/s/5dwq7G) 对象。
字符串形式的路径被解析为标识绝对或相对文件名的 UTF-8 字符序列。 相对路径将相对于 process.cwd() 指定的当前工作目录进行解析。

在 POSIX 上使用绝对路径的示例:

  1. const fs = require('fs');
  2. fs.open('/open/some/file.txt', 'r', (err, fd) => {
  3. if (err) throw err;
  4. fs.close(fd, (err) => {
  5. if (err) throw err;
  6. });
  7. });

在 POSIX 上使用相对路径(相对于 process.cwd())的示例:

  1. fs.open('file.txt', 'r', (err, fd) => {
  2. if (err) throw err;
  3. fs.close(fd, (err) => {
  4. if (err) throw err;
  5. });
  6. });

使用 [Buffer](http://nodejs.cn/s/FApxjh) 指定的路径主要用于将文件路径视为不透明字节序列的某些 POSIX 操作系统。 在这样的系统上,单个文件路径可以包含使用多种字符编码的子序列。 与字符串路径一样, Buffer 路径可以是相对路径或绝对路径:
在 POSIX 上使用绝对路径的示例:

  1. fs.open(Buffer.from('/open/some/file.txt'), 'r', (err, fd) => {
  2. if (err) throw err;
  3. fs.close(fd, (err) => {
  4. if (err) throw err;
  5. });
  6. });

在 Windows 上,Node.js 遵循每个驱动器工作目录的概念。 当使用没有反斜杠的驱动器路径时,可以观察到此行为。 例如, fs.readdirSync('c:\\') 可能会返回与 fs.readdirSync('c:') 不同的结果。 有关详细信息,参阅此 MSDN 页面

URL 对象的支持

新增于: v7.6.0
对于大多数 fs 模块的函数, pathfilename 参数可以传入 WHATWG [URL](http://nodejs.cn/s/5dwq7G) 对象。 仅支持使用 file: 协议的 [URL](http://nodejs.cn/s/5dwq7G) 对象。

  1. const fs = require('fs');
  2. const fileUrl = new URL('file:///tmp/hello');
  3. fs.readFileSync(fileUrl);

file: URL 始终是绝对路径。
使用 WHATWG [URL](http://nodejs.cn/s/5dwq7G) 对象可能会采用特定于平台的行为。

在 Windows 上,带有主机名的 file: URL 转换为 UNC 路径,而带有驱动器号的 file: URL 转换为本地绝对路径。 没有主机名和驱动器号的 file: URL 将导致抛出错误:

  1. // 在 Windows 上:
  2. // - 带有主机名的 WHATWG 文件的 URL 转换为 UNC 路径。
  3. // file://hostname/p/a/t/h/file => \\hostname\p\a\t\h\file
  4. fs.readFileSync(new URL('file://hostname/p/a/t/h/file'));
  5. // - 带有驱动器号的 WHATWG 文件的 URL 转换为绝对路径。
  6. // file:///C:/tmp/hello => C:\tmp\hello
  7. fs.readFileSync(new URL('file:///C:/tmp/hello'));
  8. // - 没有主机名的 WHATWG 文件的 URL 必须包含驱动器号。
  9. fs.readFileSync(new URL('file:///notdriveletter/p/a/t/h/file'));
  10. fs.readFileSync(new URL('file:///c/p/a/t/h/file'));
  11. // TypeError [ERR_INVALID_FILE_URL_PATH]: File URL path must be absolute

带有驱动器号的 file: URL 必须使用 : 作为驱动器号后面的分隔符。 使用其他分隔符将导致抛出错误。
在所有其他平台上,不支持带有主机名的 file: URL,使用时将导致抛出错误:

  1. // 在其他平台上:
  2. // - 不支持带有主机名的 WHATWG 文件的 URL。
  3. // file://hostname/p/a/t/h/file => throw!
  4. fs.readFileSync(new URL('file://hostname/p/a/t/h/file'));
  5. // TypeError [ERR_INVALID_FILE_URL_PATH]: must be absolute
  6. // - WHATWG 文件的 URL 转换为绝对路径。
  7. // file:///tmp/hello => /tmp/hello
  8. fs.readFileSync(new URL('file:///tmp/hello'));

包含编码后的斜杆字符(%2F)的 file: URL 在所有平台上都将导致抛出错误:

  1. // 在 Windows 上:
  2. fs.readFileSync(new URL('file:///C:/p/a/t/h/%2F'));
  3. fs.readFileSync(new URL('file:///C:/p/a/t/h/%2f'));
  4. /* TypeError [ERR_INVALID_FILE_URL_PATH]: File URL path must not include encoded
  5. \ or / characters */
  6. // 在 POSIX 上:
  7. fs.readFileSync(new URL('file:///p/a/t/h/%2F'));
  8. fs.readFileSync(new URL('file:///p/a/t/h/%2f'));
  9. /* TypeError [ERR_INVALID_FILE_URL_PATH]: File URL path must not include encoded
  10. / characters */

在 Windows 上,包含编码后的反斜杆字符(%5C)的 file: URL 将导致抛出错误:

  1. // 在 Windows 上:
  2. fs.readFileSync(new URL('file:///C:/path/%5C'));
  3. fs.readFileSync(new URL('file:///C:/path/%5c'));
  4. /* TypeError [ERR_INVALID_FILE_URL_PATH]: File URL path must not include encoded
  5. \ or / characters */

文件描述符

在 POSIX 系统上,对于每个进程,内核都维护着一张当前打开着的文件和资源的表格。 每个打开的文件都分配了一个称为文件描述符的简单的数字标识符。 在系统层,所有文件系统操作都使用这些文件描述符来标识和跟踪每个特定的文件。 Windows 系统使用了一个虽然不同但概念上类似的机制来跟踪资源。 为了简化用户的工作,Node.js 抽象出操作系统之间的特定差异,并为所有打开的文件分配一个数字型的文件描述符。
fs.open() 方法用于分配新的文件描述符。 一旦被分配,则文件描述符可用于从文件读取数据、向文件写入数据、或请求关于文件的信息。

  1. fs.open('/open/some/file.txt', 'r', (err, fd) => {
  2. if (err) throw err;
  3. fs.fstat(fd, (err, stat) => {
  4. if (err) throw err;
  5. // 使用文件属性。
  6. // 始终关闭文件描述符!
  7. fs.close(fd, (err) => {
  8. if (err) throw err;
  9. });
  10. });
  11. });

大多数操作系统限制在任何给定时间内可能打开的文件描述符的数量,因此当操作完成时关闭描述符至关重要。 如果不这样做将导致内存泄漏,最终导致应用程序崩溃。

常量

以下常量由 fs.constants 输出。
并非所有操作系统都可以使用每个常量。

文件可访问性的常量

以下常量适用于 fs.access()
常量 说明
F_OK 表明文件对调用进程可见。 这对于判断文件是否存在很有用,但对 rwx 权限没有任何说明。 如果未指定模式,则默认值为该值。
R_OK 表明调用进程可以读取文件。
W_OK 表明调用进程可以写入文件。
X_OK 表明调用进程可以执行文件。 在 Windows 上无效(表现得像 fs.constants.F_OK)。

文件系统标志

flag 选项采用字符串时,可用以下标志:

  • 'a' - 打开文件用于追加。如果文件不存在,则创建该文件。
  • 'ax' - 与 'a' 相似,但如果路径已存在则失败。
  • 'a+' - 打开文件用于读取和追加。如果文件不存在,则创建该文件。
  • 'ax+' - 与 'a+' 相似,但如果路径已存在则失败。
  • 'as' - 以同步模式打开文件用于追加。如果文件不存在,则创建该文件。
  • 'as+' - 以同步模式打开文件用于读取和追加。如果文件不存在,则创建该文件。
  • 'r' - 打开文件用于读取。如果文件不存在,则出现异常。
  • 'r+' - 打开文件用于读取和写入。如果文件不存在,则出现异常。
  • 'rs+' - 以同步模式打开文件用于读取和写入。指示操作系统绕过本地的文件系统缓存。
    这对于在 NFS 挂载上打开文件时非常有用,因为它允许跳过可能过时的本地缓存。 它对 I/O 性能有非常实际的影响,因此除非需要,否则不建议使用此标志。
    这不会将 fs.open()fsPromises.open() 转换为同步的阻塞调用。 如果需要同步的操作,则应使用 fs.openSync() 之类的。
  • 'w' - 打开文件用于写入。如果文件不存在则创建文件,如果文件已存在则截断文件。
  • 'wx' - 与 'w' 相似,但如果路径已存在则失败。
  • 'w+' - 打开文件用于读取和写入。如果文件不存在则创建文件,如果文件已存在则截断文件。
  • 'wx+' - 与 'w+' 相似,但如果路径已存在则失败。

flag 也可以是一个数字,参阅 [open(2)](http://nodejs.cn/s/ss2dGs) 文档。 常用的常量可以从 fs.constants 获取。 在 Windows 上,标志会被适当地转换为等效的标志,例如 O_WRONLY转换为 FILE_GENERIC_WRITEO_EXCL|O_CREAT 转换为能被 CreateFileW 接受的 CREATE_NEW
特有的 'x' 标志( [open(2)](http://nodejs.cn/s/ss2dGs) 中的 O_EXCL 标志)可以确保路径是新创建的。 在 POSIX 系统上,即使路径是一个符号链接且指向一个不存在的文件,它也会被视为已存在。 该特有标志不一定适用于网络文件系统。
在 Linux 上,当以追加模式打开文件时,写入无法指定位置。 内核会忽略位置参数,并始终将数据追加到文件的末尾。
如果要修改文件而不是覆盖文件,则标志模式应为 'r+' 模式而不是默认的 'w' 模式。
某些标志的行为是特定于平台的。 例如,在 macOS 和 Linux 上使用 'a+' 标志打开目录(参阅下面的示例)会返回一个错误。 而在 Windows 和 FreeBSD 上,则返回一个文件描述符或 FileHandle

  1. // 在 macOS 和 Linux 上:
  2. fs.open('<目录>', 'a+', (err, fd) => {
  3. // => [Error: EISDIR: illegal operation on a directory, open <目录>]
  4. });
  5. // 在 Windows 和 FreeBSD 上:
  6. fs.open('<目录>', 'a+', (err, fd) => {
  7. // => null, <fd>
  8. });

在 Windows 上,使用 'w' 标志打开现存的隐藏文件(通过 fs.open()fs.writeFile()fsPromises.open())会抛出 EPERM。 现存的隐藏文件可以使用 'r+' 标志打开用于写入。
调用 fs.ftruncate()fsPromises.ftruncate() 可以用于重置文件的内容。

文件拷贝常量

以下常量适用于 fs.copyFile()

常量 说明
COPYFILE_EXCL 如果目标路径已存在,则拷贝操作将失败。
COPYFILE_FICLONE 拷贝操作将尝试创建写时拷贝链接。 如果底层平台不支持写时拷贝,则使用备选的拷贝机制。
COPYFILE_FICLONE_FORCE 拷贝操作将尝试创建写时拷贝链接。 如果底层平台不支持写时拷贝,则拷贝操作将失败。

文件打开的常量

以下常量适用于 fs.open()。

常量 说明
O_RDONLY 表明打开文件用于只读访问。
O_WRONLY 表明打开文件用于只写访问。
O_RDWR 表明打开文件用于读写访问。
O_CREAT 表明如果文件尚不存在则创建该文件。
O_EXCL 表明如果设置了 O_CREAT
标志且文件已存在,则打开文件应该失败。
O_NOCTTY 表明如果路径是终端设备,则打开该路径不应该造成该终端变成进程的控制终端(如果进程还没有终端)。
O_TRUNC 表明如果文件存在且是常规文件、并且文件成功打开以进行写入访问,则其长度应截断为零。
O_APPEND 表明数据将追加到文件末尾。
O_DIRECTORY 表明如果路径不是目录,则打开应该失败。
O_NOATIME 表明文件系统的读取访问将不再导致与文件相关联的 atime
信息的更新。 仅在 Linux 操作系统上可用。
O_NOFOLLOW 表明如果路径是符号链接,则打开应该失败。
O_SYNC 表明文件是为同步 I/O 打开的,写入操作将等待文件的完整性。
O_DSYNC 表明文件是为同步 I/O 打开的,写入操作将等待数据的完整性
O_SYMLINK 表明打开符号链接自身,而不是它指向的资源。
O_DIRECT 表明将尝试最小化文件 I/O 的缓存效果。
O_NONBLOCK 表明在可能的情况下以非阻塞模式打开文件。

文件类型常量

以下常量适用于 fs.Stats 对象的 mode 属性,用于决定文件的类型。

常量 说明
S_IFMT 用于提取文件类型代码的位掩码。
S_IFREG 表示常规文件。
S_IFDIR 表示目录。
S_IFCHR 表示面向字符的设备文件。
S_IFBLK 表示面向块的设备文件。
S_IFIFO 表示 FIFO 或管道。
S_IFLNK 表示符号链接。
S_IFSOCK 表示套接字。

文件模式的常量

以下常量适用于 fs.Stats 对象的 mode 属性,用于决定文件的类型。

常量 说明
S_IFMT 用于提取文件类型代码的位掩码。
S_IFREG 表示常规文件。
S_IFDIR 表示目录。
S_IFCHR 表示面向字符的设备文件。
S_IFBLK 表示面向块的设备文件。
S_IFIFO 表示 FIFO 或管道。
S_IFLNK 表示符号链接。
S_IFSOCK 表示套接字。

文件模式的常量

以下常量适用于 fs.Stats 对象的 mode 属性,用于决定文件的访问权限。

常量 说明
S_IRWXU 表明所有者可读、可写、可执行。
S_IRUSR 表明所有者可读。
S_IWUSR 表明所有者可写。
S_IXUSR 表明所有者可执行。
S_IRWXG 表明群组可读、可写、可执行。
S_IRGRP 表明群组可读。
S_IWGRP 表明群组可写。
S_IXGRP 表明群组可执行。
S_IRWXO 表明其他人可读、可写、可执行。
S_IROTH 表明其他人可读。
S_IWOTH 表明其他人可写。
S_IXOTH 表明其他人可执行。

常用的fs对象的属性

fs.constants

返回包含文件系统操作常用常量的对象。 当前定义的特定常量在 FS 常量中描述。

常用的fs对象的方法

fs.readdirSync

  1. const paths = fs.readdirSync(path.join(__dirname))
  2. console.log(paths) // ls感觉差不多会返回当前目录下所有内容
  3. [ 'build.js', 'downloadLang.js', 'lang', 'loadLang.js' ]

fs.access(path[, mode], callback)

  • path | |
  • mode 默认值: fs.constants.F_OK。
  • callback
    • err

测试用户对 path 指定的文件或目录的权限。 mode 参数是一个可选的整数,指定要执行的可访问性检查。 mode 可选的值参阅文件可访问性的常量。 可以创建由两个或更多个值按位或组成的掩码(例如 fs.constants.W_OK | fs.constants.R_OK)。
最后一个参数 callback 是一个回调函数,调用时将传入可能的错误参数。 如果可访问性检查失败,则错误参数将是 Error 对象。 以下示例检查 package.json 是否存在,以及它是否可读或可写。

  1. const file = 'package.json';
  2. // 检查当前目录中是否存在该文件。
  3. fs.access(file, fs.constants.F_OK, (err) => {
  4. console.log(`${file} ${err ? '不存在' : '存在'}`);
  5. });
  6. // 检查文件是否可读。
  7. fs.access(file, fs.constants.R_OK, (err) => {
  8. console.log(`${file} ${err ? '不可读' : '可读'}`);
  9. });
  10. // 检查文件是否可写。
  11. fs.access(file, fs.constants.W_OK, (err) => {
  12. console.log(`${file} ${err ? '不可写' : '可写'}`);
  13. });
  14. // 检查当前目录中是否存在该文件,以及该文件是否可写。
  15. fs.access(file, fs.constants.F_OK | fs.constants.W_OK, (err) => {
  16. if (err) {
  17. console.error(
  18. `${file} ${err.code === 'ENOENT' ? '不存在' : '只可读'}`);
  19. } else {
  20. console.log(`${file} 存在,且它是可写的`);
  21. }
  22. });

不建议在调用 fs.open()、 fs.readFile() 或 fs.writeFile() 之前使用 fs.access() 检查文件的可访问性。 这样做会引入竞态条件,因为其他进程可能会在两个调用之间更改文件的状态。 相反,应该直接打开、读取或写入文件,如果文件无法访问则处理引发的错误。
写入(不推荐)

  1. fs.access('myfile', (err) => {
  2. if (!err) {
  3. console.error('myfile 已存在');
  4. return;
  5. }
  6. fs.open('myfile', 'wx', (err, fd) => {
  7. if (err) throw err;
  8. writeMyData(fd);
  9. });
  10. });

写入(推荐)

  1. fs.open('myfile', 'wx', (err, fd) => {
  2. if (err) {
  3. if (err.code === 'EEXIST') {
  4. console.error('myfile 已存在');
  5. return;
  6. }
  7. throw err;
  8. }
  9. writeMyData(fd);
  10. });

读取(不推荐)

  1. fs.access('myfile', (err) => {
  2. if (err) {
  3. if (err.code === 'ENOENT') {
  4. console.error('myfile 不存在');
  5. return;
  6. }
  7. throw err;
  8. }
  9. fs.open('myfile', 'r', (err, fd) => {
  10. if (err) throw err;
  11. readMyData(fd);
  12. });
  13. });

读取(推荐)

  1. fs.open('myfile', 'r', (err, fd) => {
  2. if (err) {
  3. if (err.code === 'ENOENT') {
  4. console.error('myfile 不存在');
  5. return;
  6. }
  7. throw err;
  8. }
  9. readMyData(fd);
  10. });

上面的“不推荐”示例会先检查可访问性,然后再使用文件。 “推荐”示例则更好,因为它们直接使用文件并处理错误(如果有错误的话)。
通常,仅在不直接使用文件时才检查文件的可访问性,例如当其可访问性是来自其他进程的信号时。
在 Windows 上,目录上的访问控制策略(ACL)可能会限制对文件或目录的访问。 但是, fs.access() 函数不检查 ACL,因此即使 ACL 限制用户读取或写入路径,也可能报告路径是可访问的。

fs.accessSync(path[, mode])

  1. // 如果可访问性检查失败,则抛出 Error。 否则,该方法将返回 undefined。
  2. try {
  3. fs.accessSync('etc/passwd', fs.constants.R_OK | fs.constants.W_OK);
  4. console.log('可以读写');
  5. } catch (err) {
  6. console.error('无权访问');
  7. }

fs.appendFile(path, data[, options], callback)