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

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

总结

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

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

相关推荐
培根芝士6 分钟前
使用 Canvas 替代 <video> 标签加载并渲染视频
前端·javascript·音视频
德育处主任Pro36 分钟前
p5.js 三角形triangle的用法
开发语言·javascript·ecmascript
小螺号dididi吹37 分钟前
菜鸟速通:React入门 01
前端·react.js·前端框架
Lstmxx1 小时前
解放前端生产力:我如何用 LLM 和 Bun.js 构建一个 YApi to TypeScript 的自动化代码生成服务
前端·ai编程·mcp
持续前行1 小时前
vue3 : 导出pdf , 除iframe之外 ,还有其他内容一并导出方式
前端·javascript·vue.js
唐某人丶1 小时前
前端仔如何在公司搭建 AI Review 系统
前端·人工智能·aigc
SenLinMu1 小时前
navigator.clipboard.writeText(text) 这段js在本地执行正常,但是在服务器执行失败时为什么?
javascript
没有鸡汤吃不下饭1 小时前
排查vue项目线上才会出现的故障
前端·vue.js·nginx
吃饭睡觉打豆豆嘛1 小时前
React Router 传参三板斧:新手也能 5 秒做决定
前端
裘乡1 小时前
storybook配合vite + react生成组件文档
前端·react.js