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

const fs = require('fs')

读取文件

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

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

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

判断文件是否读取成功:

const fs = require('fs')

let fileName = 'test.txt'
fs.readFile(fileName, 'utf-8', (err, dataStr) => {
    if (err) {
        return console.log('readFileErr: ' + err.message)
    }
    console.log('Read ' + fileName + ' success!')
    console.log(dataStr)
})

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

  • readFile()

    const { readFile } = require('fs')
    
    console.log('Program started...');
    
    let fileName = 'test.txt'
    readFile(fileName, 'utf-8', (err, dataStr) => {
        console.log('-------------');
        if (err) {
            return console.log('readFileErr: ' + err.message)
        }
        console.log('Read ' + fileName + ' success!')
        console.log(dataStr)
        console.log('-------------');
    })
    
    console.log('Program ended...');
    

    输出结果:

    Program started...
    Program ended...
    -------------
    Read test.txt success!
    Hello Node.js!
    -------------
    

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

  • readFileSync()

    const { readFileSync } = require("fs");
    
    // readFileSync() 不能注册回调函数
    var fileData = readFileSync('test.txt')
    if (fileData) {
        console.log('-------------');
        console.log('Readed Success!');
        // 没有指定字符集的话是返回一个 Buffer 对象,所以要将其转为 String
        var fileContent = String(fileData)
        console.log(fileContent);
        console.log('-------------');
    } else {
        console.log('Readed Error!');
    }
    
    console.log('Program ended...');
    

    输出:

    Program started...
    -------------
    Readed Success!
    Hello Node.js!
    -------------
    Program ended...
    

写入内容

// 1. 导入fs.writeFile() 方法
const { writeFile } = require('fs');

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

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

判断文件写入是否成功:

const { writeFile } = require("fs");

writeFile('test.txt', 'Writes new content...', (err) => {
    if (err) {
        return console.log('writeFileErr: ' + err.message)
    }
    console.log('Writing Success!')
})

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


路径动态拼接问题

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

演示路径动态拼接问题:

const { readFile } = require('fs');

readFile('./test.txt', 'utf-8', (err, data) => {
    if (err) {
        console.log(err.message);
    }
    console.log('Reading Success!');
})
console.log(data);

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

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

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

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

修改如下:

const { readFile } = require('fs');

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

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


Path 模块处理路径

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

常用的方法有:

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

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

const path = require('path')

path.join()

const path = require('path');

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

输出结果如下:

/dir1/dir2/dir4/dir5

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

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

const { readFile } = require('fs');
const { join } = require('path');

readFile(join(__dirname, 'test.txt'), 'utf-8', (err, data) => {
    if (err) {
        console.log(err.message);
    }
    console.log(data);
})

path.basename()

const path = require('path');

const fpath = '/var/local/html/blog/index.html' // 文件存放路径

/**
 * 根据路径解析文件(目录)名
 */
var fullName = path.basename(fpath)
var nameWithoutExt = path.basename(fpath, '.html')

console.log(fullName);
console.log(nameWithoutExt);

输出结果如下:

index.html
index

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

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

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

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

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

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

path.extname()

const path = require('path');

const fpath = '/var/local/html/blog/index.html' // 文件存放路径

var extName =  path.extname(fpath)
console.log(extName);

输出结果为:

.html

案例

案例1

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

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

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

编写JS:

// 1. 导入 fs
const fs = require('fs')

// 2. 读取文件内容
fs.readFile('score.txt', 'utf-8', (err, dataStr) => {
    // 3. 判断是否读取成功
    if (err) {
        return console.log('readFileErr: ' + err.message);
    }
    // console.log('Reading Success!');
    // console.log(dataStr);

    // 4.1 按照空格分割
    const arrOld = dataStr.split(' ')
    // console.log(arrOld);
    // 4.2 循环分割后的数组,对每项数据进行替换操作
    const arrNew = []
    arrOld.forEach(item => {
        arrNew.push(item.replace('=', ': '))
    })
    // console.log(arrNew);
    // 4.3 把新数组中的每项进行合并,得到新字符串
    const newStr = arrNew.join('\r\n')
    // console.log(newStr);

    // 5. 把处理完的成绩,写入新文件
    fs.writeFile('score-ok.txt', newStr, (err) => {
        if (err) {
        return console.log('writeFileErr: ' + err.message);
        }
        console.log('Writing Success!');
    })
})

处理完的结果大致如下:

小红: 99
小白: 100
小黄: 70
小黑: 66
小绿: 88

案例2

使用fspath模块将下方HTML文件拆分成对应的htmlcssjs文件,并写入到clock目录下:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>index首页</title>
  <style>
    html,
    body {
      margin: 0;
      padding: 0;
      height: 100%;
      background-image: linear-gradient(to bottom right, red, gold);
    }

    .box {
      width: 400px;
      height: 250px;
      background-color: rgba(255, 255, 255, 0.6);
      border-radius: 6px;
      position: absolute;
      left: 50%;
      top: 40%;
      transform: translate(-50%, -50%);
      box-shadow: 1px 1px 10px #fff;
      text-shadow: 0px 1px 30px white;

      display: flex;
      justify-content: space-around;
      align-items: center;
      font-size: 70px;
      user-select: none;
      padding: 0 20px;

      /* 盒子投影 */
      -webkit-box-reflect: below 0px -webkit-gradient(linear, left top, left bottom, from(transparent), color-stop(0%, transparent), to(rgba(250, 250, 250, .2)));
    }
  </style>
</head>

<body>
  <div class="box">
    <div id="HH">00</div>
    <div>:</div>
    <div id="mm">00</div>
    <div>:</div>
    <div id="ss">00</div>
  </div>

  <script>
    window.onload = function () {
      // 定时器,每隔 1 秒执行 1 次
      setInterval(() => {
        var dt = new Date()
        var HH = dt.getHours()
        var mm = dt.getMinutes()
        var ss = dt.getSeconds()

        // 为页面上的元素赋值
        document.querySelector('#HH').innerHTML = padZero(HH)
        document.querySelector('#mm').innerHTML = padZero(mm)
        document.querySelector('#ss').innerHTML = padZero(ss)
      }, 1000)
    }

    // 补零函数
    function padZero(n) {
      return n > 9 ? n : '0' + n
    }
  </script>
</body>

</html>

html-handle.js

// 1.1 导入 fs 和 path 模块
const fs = require('fs');
const path = require('path');

// 1.2 定义正则表达式
const regStyle = /<style>[\s\S]*<\/style>/
const regScript = /<script>[\s\S]*<\/script>/

// 1.3 设置目录名并创建目录
const DIR_NAME = 'clock'
const DIR_FULL_PATH = path.join(__dirname, DIR_NAME)
if (!fs.existsSync(DIR_FULL_PATH)) {
    fs.mkdirSync(DIR_FULL_PATH)
}

// 2.1 读取文件
fs.readFile(path.join(__dirname, 'index.html'), 'utf-8', (err, data) => {
    // 2.2 读取失败时
    if (err) {
        return console.log(err.message);
    }
    // 2.3 读取成功后,调用对应的3个方法解析出css,js和html文件
    resolveCSS(data)
    resolveJS(data)
    resolveHTML(data)
})

// 3.1 处理 CSS 样式文件
function resolveCSS(htmlStr) {
    // 3.2 使用正则提取页面的 <style></style>标签
    const regStr = regStyle.exec(htmlStr)
    // 3.3 将提取出来的字符串,进行替换
    // console.log(regStr);
    const newCSS = regStr[0].replace('<style>', '').replace('</style>', '')
    // console.log(newCSS);
    // 3.4 将提取的样式写入到新文件中
    var fullPath =  path.join(__dirname, DIR_NAME, 'index.css')
    fs.writeFile(fullPath, newCSS, (err) => {
        if (err) {
            return console.log(err.message);
        }
        console.log('Writing css successful!');
    })
}

// 4.1 处理 JS 样式文件
function resolveJS(htmlStr) {
    // 4.2 使用正则提取页面的 <script></script>标签
    const regStr = regScript.exec(htmlStr)
    // 4.3 将提取出来的字符串,进行替换
    const newJS = regStr[0].replace('<script>', '').replace('</script>', '')
    // 4.4 将提取的样式写入到新文件中
    var fullPath = path.join(__dirname, DIR_NAME, 'index.js')
    fs.writeFile(fullPath, newJS, (err) => {
        if (err) {
            return console.log(err.message);
        }
        console.log('Writing js successful!');
    })
}

// 5. 处理 html 文件
function resolveHTML(htmlStr) {
    // 5.1 将内嵌的 <style> 和 <script> 替换为外联的 <link> 和 <script>
    const newHTML = htmlStr
        .replace(regStyle, '<link rel="stylesheet" href="./index.css" />')
        .replace(regScript, '<script src="./index.js"></script>')
    // 5.2 将替换完成之后的 html 代码,写入到 index.html 中
    var fullPath = path.join(__dirname, DIR_NAME, 'index.html')
    fs.writeFile(fullPath, newHTML, (err) => {
        if (err) {
            return console.log(err.message);
        }
        console.log('Writing html successful!');
    })
}