
1. 项目概述1.1 为什么要做CLI-Anything先聊点实在的。我平时的工作流基本都泡在终端里写脚本、跑服务、查日志、批量改文件、搬运数据每天在命令行和编辑器之间来回切换。工具越用越多配置越堆越乱换了新机器之后还得重新折腾一遍环境效率其实全耗在重复劳动上了。CLI-Anything这个项目就是想把那些日常高频使用的命令行操作统一收拢起来做成一套即插即用的CLI工具链。它的核心目标有三件事第一把那些烦琐到不想敲第二遍的长命令封装成短命令第二把散落在各个配置文件里的脚本整理成有统一入口的工具集第三让整套工具可以在不同机器上快速复现不需要每次从头配置。这个项目不是一个大而全的框架也不是某个语言专属的脚手架它更像是一个个人效率工具箱。你有任何重复执行的命令、脚本、工作流都可以以插件的形式塞进去然后通过一个统一入口调用。适合的人群很明确日常重度使用终端的人、需要在多台机器间同步开发环境的工程师、以及想把自己的手工操作逐步自动化的人。1.2 项目解决的三个实际问题说句实话命令行工具链这种东西一个人有一个人的用法很难说哪套配置是标准答案。但CLI-Anything解决的三个问题我相信是共通的。第一个问题是命令太长太杂记不住。比如部署一个服务可能要敲十几行环境变量、参数、路径拼接隔两个星期再敲就忘了还得翻历史记录。把这类操作封装成命令之后只需要记住一个简短的名字就够了。第二个问题是脚本满地都是找不到也管不过来。我见过很多人的家目录下散落着一堆.sh文件有的叫a.sh有的叫test2.sh时间一长自己都分不清哪个是干嘛的。CLI-Anything用统一的目录结构和命令注册机制来解决这个问题每个脚本有明确的归类和描述。第三个问题是环境迁移成本高。换电脑、重装系统之后要重新配置shell、装工具、拉脚本折腾半天才能恢复到顺手的状态。CLI-Anything把整个工具链做成一个仓库配合dotfiles管理可以在几分钟之内在新机器上恢复完整的工作环境。2. 核心设计思路与工具选型2.1 为什么选择自建CLI工具链而不是直接用现成框架市面上其实有不少现成的CLI工具框架比如Python的Click、TyperNode.js的CommanderGo的Cobra随便挑一个都能快速搭出一个命令行工具。那为什么CLI-Anything不直接选一个框架而是自己搭一套轻量的壳子核心原因是一个字轻。日常终端里跑的东西绝大多数不需要完整的参数解析、帮助文档、子命令路由这些重型功能。我们需要的往往只是把一个长命令缩短、把一组操作串起来。为了这个去引入一个框架的依赖和层级结构反而增加了心智负担。CLI-Anything的做法是用Bash函数加alias做基础层用简单的目录约定做组织层。Bash自带不需要额外装任何语言运行时任何Linux发行版或macOS开箱即用。脚本内部如果需要更复杂的逻辑可以调用Perl、Python、Node等外部工具完成但外壳保持极简。这套方案的另一个好处是透明。用Click或Cobra写的工具出了问题你还得去看框架源码、理解它的装饰器和路由逻辑。而CLI-Anything每一个命令背后就是一段实实在在的Bash代码出了问题直接打开文件看哪里不对改哪里没有任何黑盒。对于想深入理解命令行工作原理的人来说这种透明感非常重要。还有一个不可忽视的因素是速度。任何解释型语言的CLI框架启动时至少要经历运行时初始化、模块加载、路由匹配这几个阶段再快也有几十毫秒到几百毫秒的延迟。而Bash函数是shell内置执行的几乎零开销按下去立刻响应。对于一天要在终端里敲几十上百次命令的人来说这种响应速度的差异体感非常明显。2.2 工具选型背后的取舍逻辑先列一下CLI-Anything用到的核心组件清单然后逐个说为什么选它们。终端环境这块我推荐的是tmux加zsh加fzf的组合。tmux做会话管理好处是断线不丢任务这个在SSH远程开发场景中几乎是刚需。zsh配合oh-my-zsh或自己写的补全配置主要图它的补全和纠错能力。fzf是模糊查找神器历史命令搜索、文件路径补全、git分支切换都靠它提升效率具体用法后面细说。核心工具方面文件搜索用的是ripgrep而不是传统的grep或ack。ripgrep的优势在于速度极快而且默认会遵守.gitignore规则不会把无关文件翻出来。日常在代码仓库里搜关键词基本是秒出结果。批量文件内容的查找和替换我习惯用sd这个小工具它的表达式语法更接近人类直觉比sed那套逃脱字符规则友好得多。文件管理用broot和joshuto。broot可以交互式地浏览目录树同时显示文件和目录在磁盘上的占比在磁盘空间分析这个场景非常好用。joshuto是Rust写的一个类ranger的文件管理器操作逻辑和vim类似键位熟悉之后浏览文件的速度非常快。目录跳转用zoxide这是之前z工具的一个现代化替代品。它根据你的历史访问频率来智能跳转比如经常访问~/project/server那么输入z server就能直接跳过去不用敲完整路径。命令和工具链的管理还依赖asdf或mise来做多语言版本管理Node、Python、Ruby、Go这些运行时版本全部通过一个工具统一管理。为什么要统一管理因为不同项目依赖的版本可能不一样系统环境变量又只有一个PATH手动切换很容易乱用版本管理工具可以在项目目录下自动切换对应版本省心得多。这些工具的共性是都是编译型语言写的、启动快、配置简单、生态活跃。我经历过从Perl脚本到Python工具再到Rust工具的更替过程现阶段这些新工具给我的体感是三个字不折腾。2.3 整体架构与延伸性设计CLI-Anything的整体架构其实很简单就是一个bin目录加一个lib目录。bin目录存放所有可执行脚本的入口lib目录存放各模块的实现逻辑不同的功能模块按子目录划分。例如这些模块之间互不依赖这是刻意为之的。在设计最初我就定下了一个原则模块之间不允许互相调用。每个模块定位为独立的工具箱目的是为了隔离故障。如果一个模块的代码写坏了只需要将它对应的入口从bin目录中移除不影响其他命令的正常工作。这在日常频繁改动脚本的场景下非常重要不小心写了个死循环脚本也只是局部损坏不至于把整个终端环境搞崩。延伸性靠的是简单的命名规范。新增一个功能的时候往lib目录下加一个子目录在bin目录下建一个同名可执行文件然后在这个文件中指定要调用哪个脚本。所有命令统一通过一个入口export const load () loadAbove保证用户看到的命令都是平级的不用记子命令。3. 核心细节解析与实操要点3.1 高频率使用的必备功能拆解先说说这个工具集里使用频率最高的几个功能快速搜索历史命令、快速启动常用项目、快速进入高频目录。快速搜索历史命令就是fzf接管CtrlR之后的效果。在终端里按下CtrlR会打开一个模糊搜索列表输入几个字母就能定位到之前敲过的某条历史命令。比如你想起昨天执行过一个带复杂参数的curl命令只记得里面有v2参数输入v2就能把它搜出来直接回车执行。这一步能把每天从history文件里手工翻找命令的时间压缩到几乎为零。快速启动常用项目则可以设计一组带项目名的命令比如p server自动进入~/project/server目录、加载这个项目对应的环境变量、启动开发服务一条命令完成过去四步操作。p是project的简写后面的参数是项目名。这里用了zoxide的z命令来做目录匹配再用一个函数检查是否存在Makefile或package.json根据项目类型自动执行对应的启动命令。快速进入高频目录则是在zoxide基础上加了自定义权重。比如把开发目录的权重设高一些把临时目录的权重设低一些这样即使临时目录访问次数更多也不会干扰开发目录的跳转体验。用一行shell函数就可以实现通过zoxide的z -w参数手动增加某个路径的权重。3.2 关键模块的运行机制CLI-Anything里最有技术含量也最值得拆解的模块是命令注册机制。这个机制决定了整个工具链如何启动、如何发现命令、以及如何解析参数。所有脚本被放在$CLI_ROOT/bin和$CLI_ROOT/lib目录下。当shell启动时会执行一个初始化脚本核心动作就是把bin目录下的所有可执行文件路径加入PATH环境变量并设置一些全局变量。具体来说初始化脚本做的事情可以用下面这段代码表达# file: init.sh export CLI_ROOT${HOME}/.local/share/cli-anything export PATH${CLI_ROOT}/bin:${PATH} for module in ${CLI_ROOT}/lib/*/; do module_name$(basename ${module}) if [[ -f ${module}/init.sh ]]; then # shellcheck source/dev/null source ${module}/init.sh fi done这里最关键的一点是不要把init逻辑放在.bashrc或.zshrc里直接堆代码而是通过source的方式引入外部文件。这样做的好处是隔离修改某个模块时不需要动shell主配置更新工具链时直接git pull整个仓库就行。参数解析的方式也值得一提。由于工具是Bash写的没有使用getopt这类标准解析库而是用了最简单的手写case匹配。这样做的原因是为了让单个脚本足够轻没有过多的抽象层读起来更直白。而且CLI-Anything的命令参数设计原则上不超过两层一个主参数加上可选的标志位。如果某个命令需要的参数过于复杂说明这个命令的定位不适合放进这个工具集应该去做成独立的脚本或服务。3.3 新增一个命令需要的步骤我这里把新增一个命令的完整流程写出来照着做一遍就能理解整个机制。第一步在lib目录下新建一个模块目录。比如要做git相关的快捷命令就创建lib/git-tools/。第二步在这个目录下创建一个脚本文件里面放真正要执行的函数。第三步在bin目录下创建一个同名入口文件内容非常简单只做一件事调用lib里对应的函数。#!/usr/bin/env bash # bin/gc # 快捷提交命令 source ${CLI_ROOT}/lib/git-tools/commit.sh git_quick_commit $第四步给文件加执行权限然后执行reload命令让工具集重新扫描。CLI-Anything里有一个reload命令它的作用就是重新source所有的init脚本让新增或修改的命令立即生效。新增命令的整个过程不会超过两分钟。这里有个值得注意的设计细节bin和lib的名字刻意保持同名这样一眼就能看出入口文件对应哪个模块。同时所有的入口文件内容都极简真正的实现逻辑全部在lib模块里。这样的拆分逻辑类似于接口与实现的分离。排查问题的时候先看bin文件确认参数传递正确再深入lib模块看业务代码定位路径非常清晰。3.4 配置体系与dotfiles同步CLI-Anything的配置体系设计为“零配置文件为主必要的配置按需生成”。每个模块允许有一份可选的config.sh文件里面只存放真正需要用户按机器调整的变量比如用户名、默认编辑器、工作区目录等。不要把包含密钥、业务密码的配置写进这个仓库。整个配置文件仓库是我用git管理的虽然仓库是私有的但少存一份敏感信息就少一分暴露风险。我的做法是敏感信息单独存到系统钥匙串或本地环境变量文件中不纳入版本管理。配置文件的管理方式采用的是symlink加git仓库的组合。一个名为dotfiles的仓库中存放所有配置文件的原始版本通过一个脚本将它们软链接到实际的家目录位置。这样所有配置文件的修改实际上是在仓库里进行的git天然记录了变更历史。坏处是如果误破坏了一个软链接可能造成配置丢失但这属于很少发生的边缘情况权衡下来利大于弊。在计算环境中配置管理和代码管理的最佳实践就是让它进入版本控制系统。这样你有完整的演进历史出了任何问题都能回溯到任意时间点。4. 实操过程与核心环节实现4.1 从零搭建一套基础版CLI-Anything直接进入实操环节。在一台干净的机器上从零开始把CLI-Anything的基础框架搭起来。首先创建目录结构mkdir -p ~/.local/share/cli-anything/{bin,lib,conf} mkdir -p ~/.local/share/cli-anything/lib/{utils,project,search,git-tools}接着创建初始化脚本。这个脚本需要被shell的rc文件引用# file: ~/.local/share/cli-anything/init.sh export CLI_ROOT${HOME}/.local/share/cli-anything export PATH${CLI_ROOT}/bin:${PATH} # 引用所有模块的init脚本 for module_dir in ${CLI_ROOT}/lib/*/; do module_name$(basename ${module_dir}) if [[ -f ${module_dir}/init.sh ]]; then source ${module_dir}/init.sh fi done # 设置常用快捷键 bind \C-r: fzf-history-widgetexec之后在.bashrc结尾加一行[[ -f ${HOME}/.local/share/cli-anything/init.sh ]] source ${HOME}/.local/share/cli-anything/init.sh然后用一个最简单的命令验证链路。创建一个bin/hi文件内容就只有一句话#!/usr/bin/env bash echo CLI-Anything is running加上执行权限执行reload然后试一下hi命令如果终端输出了CLI-Anything is running基础框架就算搭通了。4.2 核心功能模块的实现过程与代码示例接下来实现几个真正提升效率的功能模块。第一个是快速创建临时目录并进入。我日常工作经常需要临时建目录来测试东西而且希望临时目录能被自动清理掉不残留在磁盘上。# file: lib/utils/tmpdir.sh mktmp() { local tmp_dir tmp_dir$(mktemp -d ${TMPDIR:-/tmp}/cli.XXXXXX) cd ${tmp_dir} || exit 1 echo Working in ${tmp_dir} }这个脚本的好处是目录创建后立即进入完全无需手工cd。第二个是历史命令模糊搜索并执行。依赖fzf核心逻辑如下# file: lib/search/history.sh fzf-history-widget() { local selected selected$(history 1 | awk {$1; print substr($0,2)} | fzf --tac --query$READLINE_LINE) if [[ -n ${selected} ]]; then READLINE_LINE${selected} READLINE_POINT${#READLINE_LINE} fi }这个功能的速度体感非常重要fzf的延迟必须限制在100毫秒以内否则使用体验会明显下降。如果发现历史搜索变慢了优先检查history文件是否过于庞大通常超过两万条就开始影响性能可以设置HISTSIZE来控制规模。第三个是快速进入项目目录并自动加载环境。这里的逻辑比较实用直接给出一个可用的版本# file: lib/project/goto.sh p() { local project_name$1 local project_path${WORKSPACE_ROOT}/${project_name} if [[ ! -d ${project_path} ]]; then echo Project ${project_name} not found under ${WORKSPACE_ROOT} 2 return 1 fi cd ${project_path} || return 1 # 根据项目类型自动加载环境 if [[ -f .envrc ]]; then eval $(direnv export bash) elif [[ -f package.json ]]; then export NODE_ENV${NODE_ENV:-development} elif [[ -f Makefile ]]; then echo Makefile detected, run make dev to start. fi }这里我设置了环境变量WORKSPACE_ROOT作为所有项目的统一根目录比如~/workspace。进入项目目录后根据项目特征自动做环境准备这个设计在实际使用中省了很多事。第四个是git提交流程的加速命令。把add、commit、push的步骤合并成一个命令同时加上提交信息的前缀规则# file: lib/git-tools/commit.sh git_quick_commit() { local type$1 local message$2 if [[ -z ${type} || -z ${message} ]]; then echo Usage: gc type message 2 echo Example: gc feat 添加用户登录功能 2 return 1 fi git add --all git commit -m ${type}: ${message} git push }注意git push这个操作放到每次提交后自动执行最开始有人可能会不习惯但用久了之后会发现它减少了分支同步的心智负担。如果不想push可以在命令后面加一个--no-push标志git_quick_commit() { local type$1 local message$2 local do_push${3:-true} # ... 省略校验逻辑 ... git add --all git commit -m ${type}: ${message} if [[ ${do_push} true ]]; then git push fi }4.3 效率提升的进阶技巧组合命令设计基础模块搭好之后真正让效率翻倍的是组合命令的设计。我在这里分享几个自己用下来最香的组合。第一个组合是搜索加打开。在代码仓库里搜索关键词然后直接跳到匹配的文件。传统做法是先用rg搜看到路径后再用编辑器打开中间至少有三次切换。用CLI-Anything直接设计一个命令# file: lib/search/open.sh search_open() { local keyword$1 local file_path file_path$(rg --files-with-matches ${keyword} . | fzf --preview bat --coloralways {} --preview-windowright:60%) if [[ -n ${file_path} ]]; then ${EDITOR} ${file_path} fi }这里用rg找出所有包含关键词的文件再用fzf做交互式选择并且用bat在右侧预览文件内容。选完直接进编辑器。整个流程从一个命令开始全程不需要打断思路。第二个组合是查看日志文件并实时滚动。日志排查是终端高频操作传统方式是用tail加grep一行行盯。CLI-Anything里做了一个log命令支持按时间过滤、按关键字着色、自动滚动三个能力# file: lib/log/tail.sh log_tail() { local log_file$1 local keyword$2 if [[ -z ${log_file} ]]; then echo Usage: lt log-file [keyword] 2 return 1 fi if [[ -n ${keyword} ]]; then tail -f ${log_file} | grep --coloralways ${keyword} else tail -f ${log_file} fi }实际使用时还可以把journalctl、docker logs这类日志来源也封装进来统一走log_tail的接口。比如查容器日志# file: lib/log/docker.sh docker_logs() { local container_name$1 docker logs -f --tail200 ${container_name} }第三个组合是终端会话恢复。每次开机或重连SSH后把之前关闭的所有tmux会话按原样恢复。实现方式很简单在init.sh里加入一个判断# 如果环境中存在TMUX则不执行操作 if [[ -z ${TMUX} ]] [[ ${TERM_PROGRAM} ! vscode ]]; then # 尝试恢复上次的会话 if tmux has-session -t main 2/dev/null; then tmux attach-session -t main else tmux new-session -s main -d tmux attach-session -t main fi fi这个设计的思路是始终使用同一个名为main的tmux会话如果存在就附加不存在就新建。配合tmux-resurrect插件重启机器后窗口布局和运行中的任务都能完整恢复。这个组合对于长期SSH开发的人来说意义重大基本上每天的开发环境可以无缝接续。4.4 性能优化与应用效果实测很多人会觉得Bash脚本性能无所谓其实不是这样。一个封装完善的CLI工具它的启动延迟直接决定了使用者是否愿意持续使用。CLI-Anything在性能优化上做了几个关键处理。第一是避免在启动时加载大文件。init.sh里不要做任何繁重的计算或IO操作只做路径设置和轻量变量赋值。所有重量级逻辑都推迟到实际命令被调用的那一刻。这保证了即使打开了新的终端窗口也基本上感觉不到等待时间。第二是使用外部现代工具替代Bash内置功能。比如字符串替换用Bash的${var//old/new}虽然也行但处理大量文本时perl的-pi -e表达方式速度更快。文件内容查找用rg替代grep。JSON解析用jq替代sed加awk的组合。每一项都是微小的差距累积起来体感非常明显。第三是合理控制PATH规模。PATH变量中每增加一个目录所有命令查找都会慢一点。CLI-Anything构建bin目录时只暴露真正的命令入口文件而不是把整个lib下的所有脚本都扔进PATH。这样避免了环境变量的膨胀。性能优化的效果我简单测过一组数据。使用CLI-Anything的p server命令完成进入目录加加载环境约需要80毫秒而传统手工方式至少需要2到3秒再加上手动输入出错的可能性。gc命令完成一次带push的提交流程大约4秒其中3.5秒是git push本身的耗时。历史命令搜索CtrlR的响应时间在50毫秒左右。这些数据说明一个设计合理的CLI封装不会成为效率瓶颈反而会显著减少操作噪音。5. 常见问题与排查技巧实录5.1 安装与初始化阶段的高频报错第一类问题集中在首次安装和初始化阶段。最典型的报错是执行某个命令时提示command not found但明明脚本文件已经放在bin目录下了。这个问题的根源通常是执行权限没加或者PATH没有更新。新的脚本放到bin目录后必须执行chmod x bin/脚本名否则shell会觉得它不是可执行文件。PATH没更新通常是修改完init.sh后忘了执行reload命令或者新开的终端窗口没有重新加载shell配置。排查思路很简单执行echo $PATH看bin路径是否在里面执行ls -l bin/xxx看执行权限是否正常。定位到问题并不困难。第二个常见报错是执行脚本时出现bad interpreter。这个问题的原因是脚本第一行的shebang指定了解释器路径错误。比如脚本写的是#!/usr/bin/env python3但是系统里根本没有python3或者python3不在/usr/bin/env的搜索路径下。解决方式有两个要么修改shebang指向具体的解释器路径要么确保系统里有对应的解释器并且被正确安装。5.2 函数冲突你的alias改写了命令行为这个坑比较隐蔽很多人排查了很久才发现。CLI-Anything在初始化时设置了大量alias和函数而用户的.bashrc或.zshrc里可能也定义了同名函数或别名。加载顺序决定了谁生效通常shell主配置先加载CLI-Anything后加载所以工具集的配置会覆盖主配置。但如果你在系统全局配置或更深层级的配置文件里也定义了同名函数冲突就发生了。举个实际例子。有次我发现ls命令的输出格式和我预期的不一样排查了很久最终发现是oh-my-zsh的一个插件自定义了ls的alias把ls指向了ls -lhF和CLI-Anything里模块的配置冲突了。这类问题的通用解法是执行type 命令名来查看这个命令当前到底指向什么输出结果如果是命令名 is an alias或命令名 is a function就说明有覆盖关系需要找到定义位置逐层排查。5.3 输入输出交互异常与编码问题CLI工具在交互式和非交互式模式下的行为差异也常常导致奇怪的问题。比如你在终端里运行某个脚本感叹号会莫名其妙地消失或者单引号内的内容被扩展开。这是因为Bash在交互模式下会启用history expansion!字符被当作事件标记处理。解决方式是执行set H关闭这个特性或者使用双引号包裹来避免扩展行为。编码问题在涉及中文内容的脚本里更容易出现。有次在处理带中文文件名的文件时脚本里用的正则匹配怎么都不生效后来发现问题出在locale环境变量上。如果系统的locale没有设置为UTF-8Bash的通配符和正则对多字节字符的处理就会出问题。排查方法是执行locale命令检查LANG和LC_ALL的值确保它们以UTF-8结尾。5.4 效率工具的互补方案与选型思路有人可能会问用了zoxide之后还要不要装autojump用了fzf之后还有没有必要用ctrlp这类编辑器插件我的答案是效率工具不是排他的关键是选型逻辑要清晰。zoxide和autojump的功能定位基本一致都是目录跳转建议只用一个。它们的使用模式很类似没必要重复安装。但从活跃度和语法设计上看zoxide明显更新也更灵活支持通过正则匹配目标目录对模糊场景的支持更好。fzf则是和编辑器插件互补的。fzf解决的是终端里的快速搜索和选择比如历史命令、文件路径、git分支。编辑器里的模糊搜索仍然需要编辑器自己的插件来实现。两者解决的是不同层级的搜索问题。CLI-Anything不会强求所有操作都在终端里完成它更倾向于把终端里该做的事做好编辑器的事情交给编辑器去处理。6. 安全与维护提醒6.1 安全习惯与备份策略CLI工具集和系统交互越深越需要建立安全意识。这个项目里所有的脚本都有能力执行任意系统命令因此在编写脚本时必须始终考虑输入来源如果脚本接收外部参数尤其是从网络或用户输入中获取的参数必须谨慎处理。简单来说不要因为脚本封装了参数就觉得它安全了。Bash的字符串拼接和命令替换非常灵活用不好就会注入。在CLI-Anything中我严格遵守两条安全底线。第一不要让任何命令直接以root权限运行如果需要管理员权限单独提示用户执行sudo命令。第二所有涉及删除或覆写的操作在执行前打印将要执行的命令并要求用户二次确认。这两条规则写进了项目规范里每新增一个模块时都要检查是否遵守。备份策略同样重要。配置文件仓库本身就是备份。脚本仓库本身就是备份。真正需要额外关注的是那些没有纳入版本管理的运行状态比如tmux的会话布局、shell的历史记录。CLI-Anything中提供了backup命令一键将这些状态打包成压缩文件放到指定备份目录。配合定时任务可以做到每天自动备份。这个设计的初衷是重建一套工具链虽然不难但丢失运行状态和习惯配置会让效率退化好几天备份能让恢复成本降到最低。6.2 维护与版本迭代CLI工具的生命力在于持续维护。我自己的维护节奏是每周至少一次整体检查看看哪些命令的使用频率变少了哪些新出现的重复操作还没有被封装。使用频率数据从哪里来shell history就是最好的数据源。定期统计历史命令的执行次数找出那些敲得最频繁的长命令把top 10加工成新的快捷命令这个节奏能保证工具集始终贴着真实需求走。另外每当发现一个新的优秀CLI工具时不急着加进来。先观察一段时间确认它真的能稳定解决某类问题再决定是否集成。工具集不是越庞大越好每多加一个工具都意味着维护成本的上升。CLI-Anything的定位是少而精不是多而全。在平时使用过程中建议保持一个习惯经常review自己的脚本代码。Bash脚本因为语法宽松特别容易积累坏味道。发现代码越来越难读的时候或者单行命令越来越长的时候就是该重构的信号了。重构的原则我在这个项目里常用的方法是保持入口极简把计划单独立为一个文件并且给每个函数都写上注释说明它的作用、参数和返回值。代码写给人看、顺便让机器执行这句话在CLI工具链的设计里一样适用。