使用 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;
  }
}

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

总结

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

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

相关推荐
却尘10 分钟前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare11 分钟前
浅浅看一下设计模式
前端
Lee川15 分钟前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix41 分钟前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人1 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl1 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人1 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼1 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端
布列瑟农的星空1 小时前
前端都能看懂的Rust入门教程(三)——控制流语句
前端·后端·rust