FileSystem

# fs (文件系统)

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

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

当使用同步方法时,任何异常都会被立即抛出。 可以使用 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.openfs.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 指定从文件中开始读取的位置。 如果 positionnull,则数据从当前文件读取位置开始读取,且文件读取位置会被更新。 如果 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.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.mtimeprev.mtime

注意:当一个 fs.watchFile 的运行结果是一个 ENOENT 错误时,它会调用监听器一次,且将所有字段置零(或将日期设为 Unix 纪元)。 在 Windows 中,blksizeblocks 字段会是 undefined 而不是零。 如果文件是在那之后创建的,则监听器会被再次调用,且带上最新的状态对象。 这是在 v0.10 版之后在功能上的变化。

注意:fs.watch()fs.watchFilefs.unwatchFile 更高效。 可能的话,应该使用 fs.watch 而不是 fs.watchFilefs.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);
}