使用fs模块,首先需要导入:

1const fs = require('fs')

读取文件

下方是一个通过fs.readFile()读取文件的示例:

 1// 1. 导入fs模块,来操作文件
 2const fs = require('fs')
 3/**
 4 * 2. 调用 fs.readFile() 方法读取文件
 5 *  - 参数1(必须):读取文件的存放路径
 6 *  - 参数2(可选):读取文件时采用的编码格式,默认指定utf8
 7 *  - 参数3(必须):回调函数,拿到读取失败(err)和成功的结果(dataStr)
 8 */
 9fs.readFile('test.txt', 'utf-8', (err, dataStr) => {
10    /**
11     * 2.1 打印失败的结果
12     *   - 如果读取成功,则 err 的值为 null,dataStr的值为 文件的内容
13     *   - 如果读取失败,则 err 的值为 错误对象,dataStr的值为 undefined
14     */
15    console.log(err)
16    console.log('-------------')
17    // 2.2 打印成功的结果
18    console.log(dataStr)
19})

fs.readFile()用于异步读取文件。当fs.readFile()读取到文件之后,会将信息传递给err和dataStr对象。err对象可用于读取成功与否的判断,并且记录了读取失败时的信息;dataStr对象记录了读取到的文件内容。

判断文件是否读取成功:

 1const fs = require('fs')
 2
 3let fileName = 'test.txt'
 4fs.readFile(fileName, 'utf-8', (err, dataStr) => {
 5    if (err) {
 6        return console.log('readFileErr: ' + err.message)
 7    }
 8    console.log('Read ' + fileName + ' success!')
 9    console.log(dataStr)
10})

fs模块还有一个readFileSync()方法,用于同步地文件读取,它与readFile()异步地文件读取区别如下:

  • readFile():

     1const { readFile } = require('fs')
     2
     3console.log('Program started...');
     4
     5let fileName = 'test.txt'
     6readFile(fileName, 'utf-8', (err, dataStr) => {
     7    console.log('-------------');
     8    if (err) {
     9        return console.log('readFileErr: ' + err.message)
    10    }
    11    console.log('Read ' + fileName + ' success!')
    12    console.log(dataStr)
    13    console.log('-------------');
    14})
    15
    16console.log('Program ended...');
    

    输出结果:

    1Program started...
    2Program ended...
    3-------------
    4Read test.txt success!
    5Hello Node.js!
    6-------------
    

    可以发现异步地文件读取,不会立即执行回调函数,而是直接执行下一条指令,直到文件读取成功才执行回调。

  • readFileSync():

     1const { readFileSync } = require("fs");
     2
     3// readFileSync() 不能注册回调函数
     4var fileData = readFileSync('test.txt')
     5if (fileData) {
     6    console.log('-------------');
     7    console.log('Readed Success!');
     8    // 没有指定字符集的话是返回一个 Buffer 对象,所以要将其转为 String
     9    var fileContent = String(fileData)
    10    console.log(fileContent);
    11    console.log('-------------');
    12} else {
    13    console.log('Readed Error!');
    14}
    15
    16console.log('Program ended...');
    

    输出:

    1Program started...
    2-------------
    3Readed Success!
    4Hello Node.js!
    5-------------
    6Program ended...
    

写入内容

 1// 1. 导入fs.writeFile() 方法
 2const { writeFile } = require('fs');
 3
 4/**
 5 * 2. 调用 fs.wirteFile() 方法,写入文件的内容
 6 *  - 参数1(必须):文件的存放路径
 7 *  - 参数2(必须):要写入文件的内容
 8 *  - 参数3(可选):设置文件模式
 9 *  - 参数4(必须):回调函数
10 */
11writeFile('test1.txt', 'Writes new content', (err) => {
12    // 2.1 如果文件写入成功,则 err == null
13    // 2.3 如果文件写入失败,则 err == 错误对象
14    console.log(err)
15})

注:要触发文件不存在,err返回错误对象的前提是路径也不存在,否则fs.wirteFile()默认会创建一个新文件并写入。例如你当前目录下不存在test这个目录的话,可以将路径改成test/test.txt来让err返回错误对象。

判断文件写入是否成功:

1const { writeFile } = require("fs");
2
3writeFile('test.txt', 'Writes new content...', (err) => {
4    if (err) {
5        return console.log('writeFileErr: ' + err.message)
6    }
7    console.log('Writing Success!')
8})

fs.writeFile()也有与fs.readFile()类似的同步写入函数fs.writeFileSync(),作用与fs.readFileSync()类似。


路径动态拼接问题

在使用fs模块操作文件时,如果提供的操作路径是以./或../开头的相对路径时,很容易出现路径动态拼接错误的问题。

演示路径动态拼接问题:

1const { readFile } = require('fs');
2
3readFile('./test.txt', 'utf-8', (err, data) => {
4    if (err) {
5        console.log(err.message);
6    }
7    console.log('Reading Success!');
8})
9console.log(data);

切换到上层目录,执行该JS,会发现报错:

1$ cd ..
2$ node js/path-problem.js
3ENOENT: no such file or directory, open './test.txt'

这是因为代码在运行的时候,会以执行Node命令时所处的目录,动态拼接出被操作文件的完整路径。

在使用fs模块操作文件时,可以通过__dirname直接提供完整的路径,不要提供以./或../开头的相对路径,从而防止路径动态拼接的问题。

修改如下:

 1const { readFile } = require('fs');
 2
 3/**
 4 * 出现路径拼接问题,是因为使用了 './' 或 '../' 之类的相对路径
 5 * Node在运行JS时,会将这类相对路径进行处理,使用当前JS文件所在的目录拼接在打开的文件路径上
 6 * __dirname 表示当前JS文件所处的目录
 7 */
 8readFile(__dirname + '/test.txt', 'utf-8', (err, data) => {
 9    if (err) {
10        console.log(err.message);
11        return
12    }
13    console.log('Reading Success!');
14    console.log(data);
15})

再次执行,文件可以成功打开。


Path 模块处理路径

path模块是Node.js官方提供的、用来处理路径的模块。它提供了一系列的方法和属性,用来满足用户对路径的处理需求。

常用的方法有:

  • path.join():将多个路径片段拼接成一个完整的路径字符串。
  • path.basename():从路径字符串中,将文件名解析出来。
  • path.extname():从路径字符串中,将文件名扩展名解析出来。

使用之前需要先导入path模块:

1const path = require('path')

path.join():

1const path = require('path');
2
3// path.join() 将参数列表中的字符串进行路径拼接,并返回拼接结果
4const pathStr = path.join('/dir1', 'dir2/dir3', '../', './dir4', 'dir5')
5console.log(pathStr);

输出结果如下:

1/dir1/dir2/dir4/dir5

注:path.join()方法仅用于路径拼接,相当于路径字符串的处理,不对路径的存在与否进行检查。

利用path.join()替代+号拼接路径:

1const { readFile } = require('fs');
2const { join } = require('path');
3
4readFile(join(__dirname, 'test.txt'), 'utf-8', (err, data) => {
5    if (err) {
6        console.log(err.message);
7    }
8    console.log(data);
9})

path.basename():

 1const path = require('path');
 2
 3const fpath = '/var/local/html/blog/index.html' // 文件存放路径
 4
 5/**
 6 * 根据路径解析文件(目录)名
 7 */
 8var fullName = path.basename(fpath)
 9var nameWithoutExt = path.basename(fpath, '.html')
10
11console.log(fullName);
12console.log(nameWithoutExt);

输出结果如下:

1index.html
2index

path.basename(path[, ext])参数和返回值:

  • path(必选):表示要解析的路径字符串。

  • ext(可选):表示文件扩展名(后缀)。

    经过测试后你会发现,path.basename()只是将最后一段字符串解析出来,然后将其后缀中与ext相同的部分去除后返回。

  • 返回值:路径中的最后一部分(有可能是文件名,也有可能是目录名)。当指定扩展名时,返回的有可能是不包含扩展名的名称。

与path.join()类似path.basename()仅仅是做关于路径的字符串解析,并不关心实际上是否存在这样的路径。path.basename()解析到的不仅是文件名,还可以是目录名。并且path.basename()在解析后缀时,如果当前路径的basename没有相应的后缀,path.basename()会将整个basename返回。

path.extname():

1const path = require('path');
2
3const fpath = '/var/local/html/blog/index.html' // 文件存放路径
4
5var extName =  path.extname(fpath)
6console.log(extName);

输出结果为:

1.html

案例

案例1

使用fs文件系统模块,将文件中的考试数据,整理到新的文件中。

创建一个score.txt文件,内容如下:

1小红=99 小白=100 小黄=70 小黑=66 小绿=88

编写JS:

 1// 1. 导入 fs
 2const fs = require('fs')
 3
 4// 2. 读取文件内容
 5fs.readFile('score.txt', 'utf-8', (err, dataStr) => {
 6    // 3. 判断是否读取成功
 7    if (err) {
 8        return console.log('readFileErr: ' + err.message);
 9    }
10    // console.log('Reading Success!');
11    // console.log(dataStr);
12
13    // 4.1 按照空格分割
14    const arrOld = dataStr.split(' ')
15    // console.log(arrOld);
16    // 4.2 循环分割后的数组,对每项数据进行替换操作
17    const arrNew = []
18    arrOld.forEach(item => {
19        arrNew.push(item.replace('=', ': '))
20    })
21    // console.log(arrNew);
22    // 4.3 把新数组中的每项进行合并,得到新字符串
23    const newStr = arrNew.join('\r\n')
24    // console.log(newStr);
25
26    // 5. 把处理完的成绩,写入新文件
27    fs.writeFile('score-ok.txt', newStr, (err) => {
28        if (err) {
29        return console.log('writeFileErr: ' + err.message);
30        }
31        console.log('Writing Success!');
32    })
33})

处理完的结果大致如下:

1小红: 99
2小白: 100
3小黄: 70
4小黑: 66
5小绿: 88

案例2

使用fs和path模块将下方HTML文件拆分成对应的html、css和js文件,并写入到clock目录下:

 1<!DOCTYPE html>
 2<html lang="en">
 3
 4<head>
 5  <meta charset="UTF-8">
 6  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7  <meta http-equiv="X-UA-Compatible" content="ie=edge">
 8  <title>index首页</title>
 9  <style>
10    html,
11    body {
12      margin: 0;
13      padding: 0;
14      height: 100%;
15      background-image: linear-gradient(to bottom right, red, gold);
16    }
17
18    .box {
19      width: 400px;
20      height: 250px;
21      background-color: rgba(255, 255, 255, 0.6);
22      border-radius: 6px;
23      position: absolute;
24      left: 50%;
25      top: 40%;
26      transform: translate(-50%, -50%);
27      box-shadow: 1px 1px 10px #fff;
28      text-shadow: 0px 1px 30px white;
29
30      display: flex;
31      justify-content: space-around;
32      align-items: center;
33      font-size: 70px;
34      user-select: none;
35      padding: 0 20px;
36
37      /* 盒子投影 */
38      -webkit-box-reflect: below 0px -webkit-gradient(linear, left top, left bottom, from(transparent), color-stop(0%, transparent), to(rgba(250, 250, 250, .2)));
39    }
40  </style>
41</head>
42
43<body>
44  <div class="box">
45    <div id="HH">00</div>
46    <div>:</div>
47    <div id="mm">00</div>
48    <div>:</div>
49    <div id="ss">00</div>
50  </div>
51
52  <script>
53    window.onload = function () {
54      // 定时器,每隔 1 秒执行 1 次
55      setInterval(() => {
56        var dt = new Date()
57        var HH = dt.getHours()
58        var mm = dt.getMinutes()
59        var ss = dt.getSeconds()
60
61        // 为页面上的元素赋值
62        document.querySelector('#HH').innerHTML = padZero(HH)
63        document.querySelector('#mm').innerHTML = padZero(mm)
64        document.querySelector('#ss').innerHTML = padZero(ss)
65      }, 1000)
66    }
67
68    // 补零函数
69    function padZero(n) {
70      return n > 9 ? n : '0' + n
71    }
72  </script>
73</body>
74
75</html>

html-handle.js:

 1// 1.1 导入 fs 和 path 模块
 2const fs = require('fs');
 3const path = require('path');
 4
 5// 1.2 定义正则表达式
 6const regStyle = /<style>[\s\S]*<\/style>/
 7const regScript = /<script>[\s\S]*<\/script>/
 8
 9// 1.3 设置目录名并创建目录
10const DIR_NAME = 'clock'
11const DIR_FULL_PATH = path.join(__dirname, DIR_NAME)
12if (!fs.existsSync(DIR_FULL_PATH)) {
13    fs.mkdirSync(DIR_FULL_PATH)
14}
15
16// 2.1 读取文件
17fs.readFile(path.join(__dirname, 'index.html'), 'utf-8', (err, data) => {
18    // 2.2 读取失败时
19    if (err) {
20        return console.log(err.message);
21    }
22    // 2.3 读取成功后,调用对应的3个方法解析出css,js和html文件
23    resolveCSS(data)
24    resolveJS(data)
25    resolveHTML(data)
26})
27
28// 3.1 处理 CSS 样式文件
29function resolveCSS(htmlStr) {
30    // 3.2 使用正则提取页面的 <style></style>标签
31    const regStr = regStyle.exec(htmlStr)
32    // 3.3 将提取出来的字符串,进行替换
33    // console.log(regStr);
34    const newCSS = regStr[0].replace('<style>', '').replace('</style>', '')
35    // console.log(newCSS);
36    // 3.4 将提取的样式写入到新文件中
37    var fullPath =  path.join(__dirname, DIR_NAME, 'index.css')
38    fs.writeFile(fullPath, newCSS, (err) => {
39        if (err) {
40            return console.log(err.message);
41        }
42        console.log('Writing css successful!');
43    })
44}
45
46// 4.1 处理 JS 样式文件
47function resolveJS(htmlStr) {
48    // 4.2 使用正则提取页面的 <script></script>标签
49    const regStr = regScript.exec(htmlStr)
50    // 4.3 将提取出来的字符串,进行替换
51    const newJS = regStr[0].replace('<script>', '').replace('</script>', '')
52    // 4.4 将提取的样式写入到新文件中
53    var fullPath = path.join(__dirname, DIR_NAME, 'index.js')
54    fs.writeFile(fullPath, newJS, (err) => {
55        if (err) {
56            return console.log(err.message);
57        }
58        console.log('Writing js successful!');
59    })
60}
61
62// 5. 处理 html 文件
63function resolveHTML(htmlStr) {
64    // 5.1 将内嵌的 <style> 和 <script> 替换为外联的 <link> 和 <script>
65    const newHTML = htmlStr
66        .replace(regStyle, '<link rel="stylesheet" href="./index.css" />')
67        .replace(regScript, '<script src="./index.js"></script>')
68    // 5.2 将替换完成之后的 html 代码,写入到 index.html 中
69    var fullPath = path.join(__dirname, DIR_NAME, 'index.html')
70    fs.writeFile(fullPath, newHTML, (err) => {
71        if (err) {
72            return console.log(err.message);
73        }
74        console.log('Writing html successful!');
75    })
76}