node:常用的文件操作fs模块

背景

我们日常工作中,想要对前端做一些自动化操作的时候,免不了使用 node 的文件读写操作,今天来总结一下 fs 模块常用的操作。

在实际工作中,为了工程化,最好将 fs 模块进行封装,比如捕获一下错误等等,这样就不用每次都 try catch 了。可以看文末的封装

读文件:fs.readFileSync

// 一般对于路径相关的要用__dirname等操作拼接一下,如果直接写相对路径可能会有问题,比如:index.js中写了相对路径,那么执行node test/index.js 和 cd test && node index.js是不一样的。
const sourcePath = path.resolve(__dirname, './index.js')
// 这里拿到的content就是文件内容的字符串
const content = fs.readFileSync(sourcePath, 'utf-8')

读文件夹:fs.readFileSync

const sourcePath = path.resolve(__dirname, './')
const content = fs.readdirSync(sourcePath, 'utf-8')
// 返回的是文件夹下的文件数组:[ 'index.js', 'tool.js' ]

写入文件,若文件不存在则创建文件

// sourcePath:源文件路径
// outputContent:要写入的内容
fs.writeFileSync(sourcePath, outputContent, 'utf-8');

追加到文件尾部,另起一行

// sourcePath:源文件路径
// outputContent:要写入的内容
fs.appendFileSync(sourcePath, '追加的文字');

创建

创建文件

见上面写入文件

创建目录

// 创建 test 目录
const sourcePath = path.resolve(__dirname, './test')
// 创建多个目录
const sourcePath = path.resolve(__dirname, './test/test1')
fs.mkdirSync(sourcePath);

复制

复制文件

const sourcePath = path.resolve(__dirname, './index.js')
const outputPath = path.resolve(__dirname, './test.js')
fs.copyFileSync(sourcePath, outputPath);

复制目录
1、第三方库

// 使用第三方模块
var fs = require("fs-extra");
fs.copySync("./source/myDirectory", "./target/myDirectory")

2、原生方式
nodejs 从 16.7.0 版本开始,新加入了一个fs.cp()方法,可以复制目录(不管目标目录是否存在都可自动创建)。当需要复制目录时,需要将配置中的recursive属性设置为 true。

const sourcePath = path.resolve(__dirname, './source/')
const outputPath = path.resolve(__dirname, './target/')
const res = fs.cpSync(sourcePath, outputPath, {
    
     recursive: true });

3、自己写递归复制
见底部封装

移动(重命名)

const sourcePath = path.resolve(__dirname, './index.js')
const outputPath = path.resolve(__dirname, './test.js')
const res = fs.renameSync(sourcePath, outputPath);

是否存在

是否是文件

const sourcePath = path.resolve(__dirname, './index.js')
const stats = fs.statSync(sourcePath);
const isFile = stats.isFile()

是否是目录

const sourcePath = path.resolve(__dirname, './index.js')
const stats = fs.statSync(sourcePath);
const isDirectory = stats.isDirectory()

删除

删除文件

const sourcePath = path.resolve(__dirname, './index.js')
const res = fs.unlinkSync(sourcePath);

删除目录
必须是空目录才能删除,如果里面有文件此命令报错

const sourcePath = path.resolve(__dirname, './test')
const res = fs.rmdirSync(sourcePath);

示例

1、文件内容替换

将 index.js 文件中的 aaa 替换为 hhh,在 tool.js 写入以下代码:

const fs = require('fs')
const path = require('path')

function replaceFile (filePath) {
    
    
  // const sourcePath = path.resolve(__dirname, filePath)
  const sourcePath = filePath
  const content = fs.readFileSync(sourcePath, 'utf-8')
  const outputContent = content.replaceAll(/aaa/g, "hhh");
  fs.writeFileSync(sourcePath, outputContent, 'utf-8');
}
replaceFile('./index.js')

写完后执行 node tool.js ,这时 aaa 已经变为 hhh 了
在这里插入图片描述

2、文件夹内的所有文件内容替换

方案一:读取文件夹并遍历,如果是文件则读取替换,如果是文件夹则继续递归遍历。
1、获取所有的文件路径

/**
 * 获取目录下所有的文件路径(reduce方式)
 * @param {String} dir 目录路径
 * @param {Array} suffix 需要匹配的后缀,默认全量
 * @returns 所有的文件路径数组
 */
const getAllFiles = (dir, suffix) => {
    
    
  return fs.readdirSync(dir).reduce((files, file) => {
    
    
      const name = path.join(dir, file);
      const isDirectory = fs.statSync(name).isDirectory();
      if (isDirectory) {
    
    
        return [...files, ...getAllFiles(name, suffix)]
      } else {
    
    
        if (!suffix) {
    
    
          return [...files, name]
        }
        const _suffix = path.extname(name)
        if (suffix && suffix.includes(_suffix)) {
    
    
          return [...files, name]
        }
        if (suffix && !suffix.includes(_suffix)) {
    
    
          return [...files]
        }
      }
  }, []);
}
// 查找当前文件夹下,后缀为 .js 的所有文件路径
getAllFiles('./', ['.js']);
// [ 'test/test1.js', 'test.js', 'tool.js' ]
/**
 * 获取目录下所有的文件路径(普通遍历方式未完善版)
 * @param {String} dir 目录路径
 * @param {Array} suffix 需要匹配的后缀,默认全量
 * @returns 所有的文件路径数组
 */
function getFiles (dir, files_){
    
    
  files_ = files_ || [];
  const files = fs.readdirSync(dir);
  for (const i in files){
    
    
    const name = path.join(dir, files[i])
    if (fs.statSync(name).isDirectory()){
    
    
      getFiles(name, files_);
    } else {
    
    
      files_.push(name);
    }
  }
  return files_;
}
getFiles('./')

以上函数用 shell 脚本其实很方便
find . -name "*.js"

2、像例一一样,进行查找替换就可以了

// 递归执行replaceFile
function begain (_filePathList, fn) {
    
    
  return function loop (i=0) {
    
    
    if (i >= _filePathList.length - 1) {
    
    
      fn()
      return;
    }
    // 例一的函数
    replaceFile(_filePathList[i])
    i++
    loop(i)
  }
}

// 调用
const filePathList = getAllFiles('./test/', ['.js']);
begain(filePathList, () => {
    
    
  console.log('结束')
})()

方案二:使用 shell 的文件替换,node 开启一个子进程执行 shell 脚本。

const process = require("child_process");
process.exec('sh xxx.sh', (error, stdout, stderr) => {
    
    
  if (!error) {
    
    
    // 成功
  } else {
    
    
    // 失败
  }
});

例如:

find ./ -name "*.js" | xargs sed -i '' 's/aaa/hhh/g'

具体的 shell 脚本可以看 这篇

封装

import fs from 'fs';
import path from 'path';
import globPkg from 'glob';
import mkdirp from 'mkdirp';

export const copyFile = (source, target) =>
  new Promise((resolve, reject) => {
    
    
    let cbCalled = false;
    function done(err) {
    
    
      if (!cbCalled) {
    
    
        cbCalled = true;
        if (err) {
    
    
          reject(err);
        } else {
    
    
          resolve(true);
        }
      }
    }

    const rd = fs.createReadStream(source);
    rd.on('error', (err) => done(err));
    const wr = fs.createWriteStream(target);
    wr.on('error', (err) => done(err));
    wr.on('close', (err) => done(err));
    rd.pipe(wr);
  });

export const readFile = (file) =>
  new Promise((resolve, reject) => {
    
    
    fs.readFile(file, 'utf8', (err, data) =>
      err ? reject(err) : resolve(data)
    );
  });

export const writeFile = (file, contents) =>
  new Promise((resolve, reject) => {
    
    
    fs.writeFile(file, contents, 'utf8', (err) =>
      err ? reject(err) : resolve(true)
    );
  });

export const glob = (pattern: string): Promise<string[]> =>
  new Promise((resolve, reject) => {
    
    
    globPkg(pattern, (err, val) => (err ? reject(err) : resolve(val)));
  });

export const fileExist = async (pattern) => {
    
    
  const fileList = await glob(pattern);
  return fileList.length !== 0;
};

export const makeDir = async (name) => {
    
    
  await mkdirp(name);
};

export const readDir = (pattern, options): Promise<string[]> =>
  new Promise((resolve, reject) =>
    globPkg(pattern, options, (err, result) =>
      err ? reject(err) : resolve(result)
    )
  );

export const copyDir = async (source, target) => {
    
    
  const dirs = await readDir('**/*.*', {
    
    
    cwd: source,
    nosort: true,
    dot: true
  });
  await Promise.all(
    dirs.map(async (dir) => {
    
    
      const from = path.resolve(source, dir);
      const to = path.resolve(target, dir);
      await makeDir(path.dirname(to));
      await copyFile(from, to);
    })
  );
};

export const renameFile = (source, target) =>
  new Promise((resolve, reject) => {
    
    
    fs.rename(source, target, (err) => (err ? reject(err) : resolve(true)));
  });

export const rmFile = async (source) =>
  new Promise((resolve, reject) => {
    
    
    fs.unlink(source, (err) => {
    
    
      if (err) {
    
    
        reject(err);
      } else {
    
    
        resolve(true);
      }
    });
  });

export default {
    
    
  readFile,
  writeFile,
  copyFile,
  glob,
  fileExist,
  makeDir,
  renameFile,
  rmFile
};

猜你喜欢

转载自blog.csdn.net/weixin_43972437/article/details/130617548