手写t3code命令行工具:搞定Base64、URL与JSON处理
做开发这几年我发现自己最常干的事不是什么高深架构而是反复在终端里处理各种编码问题把一段乱码文本还原、把 URL 参数解出来、把一堆缩成一行的 JSON 格式化好看点。这几个操作本身不难难的是每次都临时翻文档、凑命令效率低得离谱。后来我花了一个周末自己动手写了个命令行小工具取名就叫t3code专门把 Base64、URL 编码、JSON 这三类最频繁的转换需求收拢到一个命令里。今天这篇就聊聊这个工具的完整设计思路、实现细节以及我在实际开发中踩过的那些坑。如果你也是每天要跟接口、日志、前端联调打交道的开发或者刚入门想看看一个终端工具是怎么从零落地成品的这篇文章应该够你参考了。内容不绕弯子直接从实际问题开始讲。1. 项目定位与设计思路1.1 为什么需要一个统一编码工具先说场景。日常开发里最常出现的编码需求无非三类第一是日志处理接口返回的 token、图片数据经常是 Base64有时候需要解码出来看一眼真实内容第二是 URL 调试前端传参、后端拿原始请求遇到%E4%BD%A0%E5%A5%BD这种格式脑子不转三秒根本猜不出是什么第三是 JSON 处理线上日志里经常是一整行无缩进的 JSON用编辑器格式化要复制粘贴好几趟体验很差。这三件事单拎出来都有现成方案比如 Linux 的base64命令、Python 的urllib、jq工具但它们分散在不同生态里命令语法、参数风格都不统一组合使用还要考虑管道兼容。我的诉求很简单一个命令三种核心编码能力统一参数风格支持管道输入装一次就能到处用。于是t3code这个项目就定了调子不追求功能多而是把高频场景做到极致顺手。1.2 “t3code”这个名字怎么来的不少朋友第一次看到这个名字都会问 “t3” 是什么。其实最初我想叫 “codex”但这个名字容易被误解成别的项目。后来设计功能时我意外发现核心模块正好三个Base64、URL、JSON而且文件夹里也有三个主要源码文件。干脆就叫t3code即Triple-Code的简写寓意一句话三重编码一次搞定。这个名字还带来了一个意外好处命令短敲起来顺手在 zsh 里配置了 alias 之后几乎不用想就能盲打。命名这件事虽然看起来小但对工具类项目的影响其实挺大。一个好记、好拼、语义清晰的名称决定了你想不想用它第二次。我见过太多工具功能不错但名字又长又绕最后默默被遗忘的案例。所以如果你也在做自己的小工具命名这一关值得多花十分钟。1.3 功能边界三合一而不是全家桶很多工具做着做着就膨胀了今天加个 AES 加密明天加个 HASH 计算最后变成一个大杂烩。我特意给t3code立了一条规矩只做文本编码和格式转换绝不碰二进制处理、加密算法、文件操作这些外围功能。理由很简单二进制处理有 XXD、加密有 OpenSSL重复造轮子没有意义。专注文本编码这个垂直场景才能保证代码质量、使用手感和用户心智清晰。从实际经验来看窄功能边界的工具还有一个隐形优点出了问题排查范围小。比如某个编码结果不对我只需要怀疑t3code这一个命令而不是怀疑一串复杂的调用链。这点在紧张排障的时候尤其重要。2. 三种核心编码的实现原理与代码2.1 Base64 编解码中文处理是第一道坎Base64 的原理一句话能说清把每 3 个字节变成 4 个可打印字符用 64 个 ASCII 字符表示 6 位二进制数据末尾用补齐。实现上我用 Python 标准库的base64模块但第一版代码就踩了个坑直接对中文字符串调用b64encode会报类型错误因为b64encode接收的是 bytes 类型不认 str。正确的姿势是先把字符串按 UTF-8 编码成字节流再交给 Base64解码后拿到的 bytes也要用 UTF-8 还原成字符串。对应代码如下# 编码 data 你好t3code encoded base64.b64encode(data.encode(utf-8)).decode(ascii) print(encoded) # 解码 decoded base64.b64decode(encoded).decode(utf-8) print(decoded)为什么必须.encode(utf-8)因为 Base64 本身处理的是字节不关心字符集。中文在 UTF-8 下占 3 个字节在 GBK 下占 2 个字节同样的字编码出来的 Base64 结果完全不同。项目里我强制统一 UTF-8减少了大量因环境差异导致的隐性 bug。2.2 URL 编解码quote 与 quote_plus 的差别URL 编码的核心是把非 ASCII、空格、保留字等转为%XX形式。Python 的urllib.parse提供了quote和quote_plus两个函数初学者很容易混。关键区别在空格的处理quote会把空格转成%20而quote_plus会把空格转成加号。这个差异在某些场景能坑到怀疑人生。比如在application/x-www-form-urlencoded的表单提交中空格必须编码为而在普通 URL 路径里应该用%20。我最终的设计是给t3code加了一个--plus参数默认用quote需要提交表单数据时手动加--plus避免一刀切带来的兼容问题。核心逻辑如下import urllib.parse def url_encode(text, plusFalse): if plus: return urllib.parse.quote_plus(text) return urllib.parse.quote(text) def url_decode(text, plusFalse): if plus: return urllib.parse.unquote_plus(text) return urllib.parse.unquote(text)这里还有一个容易忽略的操作顺序问题解码时不要先手动把%替换成%25除非你是想处理被双重编码过的数据。我之前在对接某个老系统时就遇到过一次双编码问题对方把整个 URL 编码了两次解码一次后看着还是乱码必须连续解两次才能拿到原文。这个情况在工具里我没有自动化处理因为没法可靠判断输入是否双重编码但不妨在文档里给大家提个醒。2.3 JSON 格式化与压缩别被 ensure_ascii 坑了JSON 处理看着简单无非就是json.dumps和json.loads但有一个参数必须重点提ensure_ascii。默认情况下这个参数是True就是说所有非 ASCII 字符都会转成\uXXXX形式如果直接用它做格式化中文全变成一串转义字母完全不可读。我在t3code里的做法是强制把ensure_ascii设为False同时保证输入和输出都以 UTF-8 写回。格式化时用indent2和ensure_asciiFalse压缩时用separators(,, :)去掉多余空格。代码片段如下import json def format_json(text): obj json.loads(text) return json.dumps(obj, indent2, ensure_asciiFalse) def compress_json(text): obj json.loads(text) return json.dumps(obj, separators(,, :), ensure_asciiFalse)另外JSON 格式化还有一个边界情况有时候输入是单引号包着的 JSONPython 标准库的json.loads不认单引号会直接报错。这算是一个高频问题后面我会在排查板块专门讲怎么处理。3. CLI 工具落地从脚本到可安装命令3.1 参数设计一眼就懂顺手就好命令行工具最忌讳的就是参数难记。我把t3code的子命令设计成三个动作对应三种编码每个子命令下统一用-e/-d表示编码/解码。JSON 子命令稍作扩展用-f表示格式化、-c表示压缩。整体结构预览如下t3code b64 -e helloBase64 编码t3code b64 -d aGVsbG8Base64 解码t3code url -e 你好 worldURL 编码t3code url -d %E4%BD%A0%E5%A5%BDURL 解码t3code json -f {a:1}JSON 格式化t3code json -c {a: 1}JSON 压缩这种子命令的优点是逻辑清楚以后想扩展新编码比如 Hex直接加一个hex子命令就行不会破坏已有命令的兼容性。同时我让所有子命令都支持从管道读入数据比如接口返回已经被 Base64 过的内容可以直接cat data.txt | t3code b64 -d完成解码不需要手动复制粘贴效率提升明显。3.2 参数解析与主入口实现参数解析我选了 argparse虽然代码比 click 啰嗦一点但胜在零依赖、标准库自带、每个开发者都能一眼看明白。主入口设计了统一的读取逻辑优先取命令行参数如果参数为空再读标准输入。这里有个细节值得注意读标准输入时要用.strip()去掉末尾换行符否则管道方式输入的内容最后总带着一个\n编码结果会跟手动输入不一致。import sys, argparse def read_input(text): if text: return text.strip() if not sys.stdin.isatty(): return sys.stdin.read().strip() raise ValueError(请输入内容或从管道传入数据) def main(): parser argparse.ArgumentParser(descriptiont3code 三合一编码工具) subparsers parser.add_subparsers(destcommand, requiredTrue) b64 subparsers.add_parser(b64, helpBase64编解码) b64.add_argument(-e, actionstore_true, help编码) b64.add_argument(-d, actionstore_true, help解码) b64.add_argument(text, nargs?, default) url subparsers.add_parser(url, helpURL编解码) url.add_argument(-e, actionstore_true, help编码) url.add_argument(-d, actionstore_true, help解码) url.add_argument(--plus, actionstore_true, help空格编码为) url.add_argument(text, nargs?, default) jsonp subparsers.add_parser(json, helpJSON格式化/压缩) jsonp.add_argument(-f, actionstore_true, help格式化) jsonp.add_argument(-c, actionstore_true, help压缩) jsonp.add_argument(text, nargs?, default) args parser.parse_args() try: text read_input(args.text) if args.command b64: result handle_base64(text, args.d) elif args.command url: result handle_url(text, args.d, args.plus) else: result handle_json(text, args.f) print(result) except Exception as e: print(f[错误] {e}, filesys.stderr) sys.exit(1)注意我给每个解析器里的text都设了nargs?和default这样既能支持t3code b64 -e content也能支持空参数时从管道读取两套输入方式互不冲突。3.3 打包安装与本地环境配置Python 脚本的分发是个老问题。为了让t3code能像系统命令一样随处调用我用pyinstaller打包成了单文件二进制然后丢到/usr/local/bin下。打包命令行很简单pip install pyinstaller pyinstaller -F -n t3code src/main.py编译完用t3code --help验证一下就完事了。这里有个经验PyInstaller 打包的二进制体积偏大但换来的是执行效率高、不依赖目标机器 Python 环境。如果你自己用还可以不改系统的 Python 环境直接在项目目录做软链chmod x src/main.py ln -s $(pwd)/src/main.py /usr/local/bin/t3code这两种方式我都试过PyInstaller 的更适合分享给别人软链的方式适合快速迭代开发。反正核心代码都集中在src/main.py一个文件里更新逻辑只需要把软链指向的文件改掉即可。4. 实际使用中的坑与排查实录4.1 中文乱码明明解码成功却显示“烫烫烫”Base64 解码后乱码是最高频的问题。比如遇到5L2g5aW9解码后得到 bytes如果你不指定编码直接打印终端可能会用一个错误的编码去解释它结果就是乱码或者你看到了一串数字和字母组合的 Unicode 转义字符。解决办法很简单解码时强制用 UTF-8 解码并且捕获 UnicodeDecodeError提示用户输入内容可能不是 UTF-8 编码。我还遇到过一种情况终端本身是 GBK 环境但 Python 脚本往 stdout 输出 UTF-8 中文导致显示乱码。为此我在脚本开头加了环境变量修正import sys, io sys.stdout io.TextIOWrapper(sys.stdout.buffer, encodingutf-8, errorsreplace)这个处理对跨平台部署很有帮助尤其在 Windows 的旧版 cmd 里不修一下 stdout 编码输出中文必定炸。4.2 URL 编码后空格和加号混淆这个坑在接口联调时最明显。我调试一个老系统的回调地址时对方要求表单编码把空格编码成我默认用的quote生成了%20结果服务端怎么都校验不过日志里显示参数被空格分隔成了两个字段。排查到最后发现就是空格编码方式不一致。所以我在工具里单独加了这个规则并且输出了一条提示当用户用--plus时会显示推荐场景减少误用。说实话这类问题不属于编码实现错误而是协议语义的差异。大家在做 URL 相关功能时一定要先搞清楚你对接的系统用的是标准路径编码还是表单编码这才是排查方向。4.3 JSON 单引号和尾逗号问题线上日志里经常能看到这种 JSON{name: test, age: 20,}。它看着像 JSON但 Python 标准库的json.loads会直接报错因为标准 JSON 要求双引号和不要尾随逗号。处理这种“不标准 JSON”我用ast.literal_eval做了一层兜底它是 Python 里处理字面量结构的神器能解析单引号字符串和尾随逗号而且比eval安全得多。实现逻辑很简单先尝试标准的json.loads如果失败再用ast.literal_eval尝试解析再失败才报错。这个兜底在实际使用中救了我好几次特别是在处理其他团队输出的半格式化数据时。不过要注意ast.literal_eval解析出来的可能不是纯 JSON 类型还需要二次检查保证是 dict 或 list。4.4 常见问题速查表现象可能原因解决办法Base64 解码出来是乱码输入内容是 GBK 而非 UTF-8先确认编码或用iconv转换URL 解码后空格变使用了解码函数但未选对模式需要表单解引用--plus模式JSON 格式化后中文变成\uXXXXensure_ascii默认开启在json.dumps中设置ensure_asciiFalseJSON 解析报错输入含单引号或尾随逗号用ast.literal_eval做兜底解析管道输入时编码结果多一个换行未对 stdin 内容做 strip读取后调用.strip()PyInstaller 打包后杀毒误报单文件打包特征被标记尝试用--onefile附带加壳或改用软链方案5. 个人经验与后续联动扩展5.1 工具开发的隐藏收益是“复杂度教育”最后说点工具本身以外的感受。开发t3code的最大收获不是多了一个顺手的小命令而是让我重新理解了什么叫“干什么事都要先定边界”。三个功能看起来简单但真正要把每个细节都处理好比如编码选择、参数兼容、输入方式、异常提示工作量一点不小。这个工具也让我养成了一个习惯不但在代码里用还在浏览器控制台、Postman 脚本、甚至和同事的临时沟通里都用它做快速换算。5.2 一个关于 alias 的补充技巧分享一个个人特别喜欢的小配置在.zshrc里给t3code配上核心 aliasalias b64t3code b64、alias uet3code url -e、alias udt3code url -d这样终端里的实际输入可以短到三个字符。管道协同使用时比如从接口响应里抓 Base64 字段后直接解码一条命令就能完成全链路转换实测下来效率提升非常可观。5.3 后续扩展的几个方向目前t3code只覆盖了三个基础能力但我已经在规划下一批功能了。比如 Hex 编码、JWT 的 Payload 解码、时间戳转换甚至 XML 到 JSON 的简易转换。这些功能都和“文本格式变换”这个主题一致不会破坏工具边界。如果你也在写类似的开发者小工具我建议你坚持一个原则功能可以多但必须归属同一个清晰主题这样每次有什么编码问题你脑子里浮现的第一个工具就是它。