使用 oclif 开发命令行工具实现批量修改文件名

前言

前端开发工作中常用的很多 CLI 命令相信大家已经很熟悉了,很方便很实用,能够快速帮助你创建项目,快速执行某些重复性操作。

oclif

我们 github.com/oclif/oclif 库来作为 CLI 的基础框架,这个官方文档写的很详细,我们这里简单的扩展说一下,并且实践一个 demo。

设计一个批量修改文件名的小工具

我们设计一个批量更改文件名字的小工具,它可以帮助我们将某个目录的下所有文件,按照自定义的规则来进行批量重命名,这个工具

设计我们的命令用法

我们希望这样使用命令即可批量重命名目录下的所有文件。

bash 复制代码
./cl rename ./test  -R -N test_{{index}}_{{time}}_{{st}}

其中 ./cl 是 ln -s /bin/dev

我们想设计的参数

  • {{name}} 代表原始文件名
  • {{index}} 代表递归指数:1,2,3,4
  • {{time}} 代表时间:2022-8-23_11-39
  • {{st}} 代表时间戳:1661226082464
  • -R 代表是否开启目录深层递归
  • -N XXXX 批量重命名后的名字格式

列如我们可以这样用:

bash 复制代码
./cl rename ./test  -R -N test_{{index}}_{{time}}_{{st}} 

正在执行批量重命名命令,参数:{ path: './test' },{ deep: true, name: 'test_{{index}}_{{time}}_{{st}}' }
读取目录: ./test
读取目录: test/sub
读取目录: test/sub/commands
读取目录: test/sub/commands/hello
重命名文件: test/sub/commands/hello/test_4_2022-8-18_19-47_1661225596495.ts --> test_0_2022-8-18_19-47_1661225679878.ts
重命名文件: test/sub/commands/hello/test_5_2022-8-18_19-47_1661225596495.js --> test_1_2022-8-18_19-47_1661225679879.js
重命名文件: test/sub/commands/hello/test_6_2022-8-18_19-47_1661225596496.ts --> test_2_2022-8-18_19-47_1661225679879.ts
重命名文件: test/sub/commands/hello/test_7_2022-8-18_19-47_1661225596496.js --> test_3_2022-8-18_19-47_1661225679879.js
重命名文件: test/sub/commands/hello/test_8_2022-8-18_19-47_1661225596496.ts --> test_4_2022-8-18_19-47_1661225679879.ts
重命名文件: test/sub/commands/hello/test_9_2022-8-18_19-47_1661225596496.js --> test_5_2022-8-18_19-47_1661225679879.js
读取目录: test/sub/commands/setup
重命名文件: test/sub/commands/setup/index.d.ts --> test_6_2022-8-18_19-47_1661225679879.ts
重命名文件: test/sub/commands/setup/index.js --> test_7_2022-8-18_19-47_1661225679879.js
重命名文件: test/sub/index.d.ts --> test_8_2022-8-18_19-47_1661225679879.ts
重命名文件: test/sub/index.js --> test_9_2022-8-18_19-47_1661225679880.js
重命名文件: test/sub/test_3_2022-8-23_10-52_1661225596495 --> test_10_2022-8-23_10-52_1661225679880
重命名文件: test/test_0_2022-8-23_10-52_1661225596494 --> test_11_2022-8-23_10-52_1661225679880
重命名文件: test/test_1_2022-8-16_18-1_1661225596495.png --> test_12_2022-8-16_18-1_1661225679880.png
重命名文件: test/test_2_2022-7-18_14-32_1661225596495.json --> test_13_2022-7-18_14-32_1661225679880.json
重命名文件: test/图片2.jpg --> test_15_2022-6-24_15-12_1661225679880.jpg
重命名文件: test/矩阵点背景图 2.png --> test_17_2022-8-12_17-23_1661225679881.png
重命名文件: test/矩阵点背景图.png --> test_18_2022-8-12_17-23_1661225679881.png

自动的递归所有文件,并且全部以某个格式进行命名

源码实现

目录结构

首先跟随官方文档所说的那样,安装并生成 Hello world 程序,再进行我们自定义命令的编写。接下来我们新建一个命令。

  • src/commands/rename/index.ts
  • 在 rename 目录下新建 index.ts 之后,框架会为我们自动注册 rename 命令。

代码中我写了注释,通过配合代码阅读相信会更有用。

typescript 复制代码
import { Command, Flags, CliUx } from "@oclif/core";
import * as fs from "fs-extra";
import * as path from "path";

// 执行命令:
// ./cl rename ./test  -R -N test_{{index}}_{{time}}_{{st}}
export class Rename extends Command {
  static description = "description of this example command";

  // 注册标识参数:--name 和 --deep,并且设置简称
  static flags = {
    name: Flags.string({ char: "N" }),
    deep: Flags.boolean({ char: "R" }),
  };

  // 注册参数,即 rename 空格后的第一段文字,这里是 ./test
  static args = [{ name: "path" }];

  private index = 0;

  // 命令解析完毕后,执行此方法
  async run() {
    const { args } = await this.parse(Rename);
    const { flags } = await this.parse(Rename);

    this.log("正在执行批量重命名命令,参数:%s,%s", args, flags);

    // 遍历目录并给每一个文件执行规则程序
    this.wake(args.path, flags.deep, (path: string) => {
      this.rule(path, flags.name);
    });
  }

  // 递归遍历目录
  wake(root: string, recursive = false, callback = (path: string) => {}): void {
    console.log("读取目录:", root);
    for (const filename of fs.readdirSync(root)) {
      const absPath = path.normalize(path.join(root, filename));
      const file = fs.statSync(absPath);
      if (file.isDirectory() && recursive) {
        this.wake(absPath, recursive, callback);
        continue;
      }
      try {
        callback(absPath);
      } catch (error) {
        console.log(error);
      }
    }
  }

  // 文件名规则注册
  rule(filePath: string, nameRules?: string) {
    const fileName = path.basename(filePath);

    // 规则程序执行,替换关键字
    // 目前只做这么几个规则,以后看情况加,扩展性也不错,可以单独分到 services 层做一层分离,可读性更高
    // 这里注册了 {{index}} {{time}} 等关键字
    let newFileName = this.keyword(
      String(nameRules),
      "index",
      () => this.index++
    );
    newFileName = this.keyword(newFileName, "name", () => fileName);
    newFileName = this.keyword(newFileName, "st", () => new Date().getTime());
    newFileName = this.keyword(newFileName, "time", () => {
      const date = new Date(fs.statSync(filePath).mtime);
      return `${date.getFullYear()}-${
        date.getMonth() + 1
      }-${date.getDate()}_${date.getHours()}-${date.getMinutes()}`;
    });

    newFileName += path.extname(filePath);
    const absNewPath = path.normalize(
      path.join(path.dirname(filePath), newFileName)
    );

    this.log(`重命名文件: ${filePath} --> ${newFileName}`);
    fs.renameSync(filePath, absNewPath);
  }

  // 文件名关键字替换
  keyword(
    fileName: string,
    rule: string,
    variableFn: () => string | number
  ): string {
    const key = "{{" + rule + "}}";
    const variable = variableFn();
    while (fileName.includes(key)) {
      fileName = fileName.replace(key, String(variable));
    }
    return fileName;
  }
}

执行命令之后的结果都在上面的【命令用法】章节了,可以返回到上面看看输出。

总结

总的来说脚手架这个东西在以后都是非常重要的,因为很多事情都不可能是从零开始,写个脚手架整合一些自己常用的东西,可以很方便的写一些脚本辅助日常工作。

当然,我这篇文章演示的只是冰山一角,还有更多的花样,比如它还能输出表格,选择框,特效,动画,进度条之类的,有很大的探索价值。

相关推荐
腾讯TNTWeb前端团队7 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰10 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪10 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪10 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy11 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom11 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom12 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom12 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom12 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom12 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试