用 Node.js 写一批顺手的命令行小脚本
发布 分类 engineering
当前浏览器未执行 JavaScript,下面是本篇的 Markdown 原文,内容一字未改。
命令行工具的价值不在于功能多,而在于**把它变成肌肉记忆**。下面是我每天真的在用的几个,都只用 Node 内置模块。
<!-- more -->
## 骨架:三个内置模块开箱
```js
import { readdir, readFile, writeFile } from 'node:fs/promises';
import { resolve, basename, extname } from 'node:path';
import { fileURLToPath } from 'node:url';
const args = process.argv.slice(2);
const flags = new Set(args.filter(a => a.startsWith('-')));
const params = args.filter(a => !a.startsWith('-'));
```
参数解析就这么点东西。真需要 `-p 8080` 这种带值的形式,再多写五行就够。
## 脚本一:批量改文件名
把 `DSC_0001.JPG` 这类相机命名统一成 `2024-05-20-trip-01.jpg`:
```js
const files = (await readdir(dir)).filter(f => /\.(jpe?g|png)$/i.test(f)).sort();
for (const [i, f] of files.entries()) {
const n = `${prefix}-${String(i + 1).padStart(2, '0')}${extname(f).toLowerCase()}`;
await rename(resolve(dir, f), resolve(dir, n));
}
```
> [!CAUTION]
> 批量改名一定要先跑 `--dry` 打印将要发生的操作。我第一次写这个脚本没做演练,重命名顺序写错,把一半照片覆盖了。
## 脚本二:给 Markdown 生成目录
扫描 `##` 到 `####` 标题,输出可直接粘贴的列表,顺便把锚点按同一套规则生成(中文保留、空格转连字符、重复加序号),保证和渲染器算出来的 id 完全一致。
## 脚本三:本地预览服务器
`node:http` + 正确的 MIME + 目录回退,四十行搞定:
```js
const types = { '.html': 'text/html; charset=utf-8', '.css': 'text/css', '.js': 'text/javascript', '.json': 'application/json', '.svg': 'image/svg+xml' };
createServer(async (req, res) => {
let p = decodeURIComponent(new URL(req.url, 'http://x').pathname);
let file = resolve(root, '.' + p);
if (!extname(file)) file = await pickHtml(file); // /about -> /about.html
...
}).listen(port, () => console.log('http://127.0.0.1:' + port));
```
记得把 `Cache-Control` 设成 `no-store`,否则改完 CSS 刷新看不到变化时会怀疑人生。
## 通用设计原则
1. **默认值要好**:不带参数就应该是最常用的行为。
2. **危险操作用显式开关**:删除、覆盖类操作必须传 `--yes`。
3. **输出要能读**:打印相对路径而不是绝对路径,打印字节数而不是「成功」。
4. **退出码要正确**:失败就 `process.exitCode = 1`,否则 CI 里脚本永远「成功」。
## 放哪里
我会把脚本放在仓库的 `scripts/` 下,并在 `package.json` 里挂一行 `"scripts": { "serve": "node scripts/serve.mjs" }`。这样换电脑、换同事都能一条命令跑起来。