[AI时代码农生存指南]Rust编写CLI 01. CLI的复古轮回

前言

虾神最早接触计算机是在1995年,那时候还是一个青葱水嫩初中生,虾老爹是老家一家职业中专的老师,那个学校开设了电算会计课程,所以有了全县第一个计算机机房。那年暑假,虾神第一次接触了计算机,在那个没有互联网,甚至还没有图形化界面的时代,黑屏上那个闪烁着的光标,代表了一种科技的神秘......

我到今天还一直记得,使用WPS之前,先要敲ucdos命令,加载中文环境,才能够正常显示......

之后,几年,陆陆续续开始接触了更多的计算机知识,从命令行到图形界面,从记忆wps的都快捷键,到鼠标点点点......一直到大学,选择了当时超级爆火的计算机专业......

从第一次接触计算机,到今天,三十多年过去了,没想到兜兜转转,又回来了命令行。

什么是CLI

CLI(Command Line Interface)是命令行界面的缩写,它是一种通过计算机的命令行界面来与计算机交互的方式。

从使用上看,CLI实际上并不符合人类的使用习惯,你需要记忆指令、参数、选项等,然后通过键盘去输入指令,然后等待计算机执行。

在windows上大家可能用得少(废话,windows的意思就是窗口,主打图形化界面),但是在linux的使用上,绝大部分都是使用的CLI。例如我们要在某个目录中,列出符合某个条件的文件,就可以使用ls命令。例如:

复制代码
ls -l

还可以通过参数,进行筛选,例如我们就看4月份的日志文件:

但是GUI则不一样了:GUI本身就是是给人看的,本质上是一个信息翻译工具,把人类的习惯,翻译成计算机能够理解的指令。一般情况下,没有人需要去专门学习GUI的操作方式,它就是依照人类的习惯,来设计的。

例如我们要把文本的字体设置为黑体加粗:

所以对比一下就可以发现,AI更适用于使用CLI而非GUI。

AI的泥头车就这样轰隆隆的开过来了,把所有的非遗古法编程都碾在车轮下。

从functioncall,到MCP,到Skills,再到OpenClaw,我们对于AI的应用一直在从对话转向行动,特别是MCP的出现,一度让大家认为AI的底层操作模式已经打通了。

但是为什么仿佛一夜之间,又回到了CLI?

从大模型的诞生,本身就是建立在海量代码和数据基础上,而且人类对于大模型的研究和期望,一直都是输出-输出,而非中间的运算过程,所以CLI 本质上就是 AI 的母语。 CLI 具备文本输入、结构化输出、报错清晰、易于组合以及方便自动化等一系列得天独厚的优势。先别说Anthropic 这种起始就一直坚持用cli的厂商,近期飞书、钉钉、企业微信、 Google 、 Stripe 等巨头,都快速上线和开源路自己的 CLI 产品。

本来MCP是想做成AI大模型的"标准工具箱"的,目的很简单,就是让大模型能通过它和外界互动,把活儿干完。但真到实际用起来才发现,MCP有三个大毛病,根本绕不开,也正因为这样,开发者们都纷纷放弃它,转而去用CLI了,具体毛病如下:

1. MCP专烧Token没上限,CLI却是精打细算

MCP最致命的问题,就是特别占上下文,即烧Token烧得特别厉害。它得把所有工具的名字、参数格式,还有怎么调用的例子,事无巨细全都一股脑、死板地塞进上下文里(你如果不塞完整了,说不得大模型调用的时候就给你整个:"啥啥啥?你说啥?俺不中捏"),多增加一个工具,Token的消耗就会暴涨。

但CLI就不一样了,好的cli都出厂自带"自我说明"的功能。Agent不用一下子学会所有命令,什么时候不会用了,输个-h(也就是help命令),就能随时查用法、学操作。这种"用到哪学到哪"的方式,既能大大减少Token的消耗,还能保证调用的时候不出错。

经过实际测试,用官方的CLI,不管处理什么任务,Token消耗都比MCP少好几倍。

2. MCP是让人头疼的"黑盒子",CLI却能让人机配合得很顺畅

MCP更像是专门给Agent量身做的"私有协议",对咱们人类开发者来说,它就跟个黑盒子似的------整个运行过程都在Agent内部,一旦出了错,咱们人类想在自己的电脑上重现这个错误、找出问题在哪,难上加难,根本没法调试。

所以MCP干成一件事,就像上香许愿,先别说最终成不成,但凡哪个步骤有问题,你都没法排查......

再看CLI,它对Agent和人类都很友好。要是Agent用CLI的时候出了错,咱们人类只要把那行出错的命令复制下来,在自己的终端里运行一遍,就能马上知道错在哪,还能帮AI把问题修好。

3. MCP调用起来又麻烦又笨拙,CLI的管道符能轻松搞定复杂活儿

任何一个工作,如果不能够一步完成,就涉及到了多个工具的组合调用,这种情况下,MCP就比较麻烦了。

首先他要对于每个工具的输入要进行计算,然后每个输出都要进行解析。不停的序列化和反序列化,那是妥妥的烧token如加州大火......

但是CLI则不然,它有个绝活,就是管道符操作(也就是|这个符号)。简单的来说,就是它能把好几个命令拼起来,做成一条"流水线",上一个命令的输出,能直接当成下一个命令的输入,不但不用额外操作,而且在计算机内部还是实现的是零拷贝,几乎没有任何开销。

举个例子,比如我们要从dem中把等值线提取出来并且渲染成图,那么首先可以有一个命令是读取数据的,然后解析空间信息,之后提取等值线,最后渲染并且导出成图片,用CLI的话,就只要"一条命令",像接水管一样拼起来,就能轻松搞定这些复杂需求。 如下所示:

伪码示例

复制代码
read_tif --input dem | contour --output contour.shp | renderer --output image.png

这一行一共调用了三个命令,分别是read_tif、contour、renderer。

  • read_tif:读取dem文件,输出空间信息。

  • contour:提取等值线,输出等值线文件。

  • renderer:渲染等值线文件,输出图片。

每一个命令,都有自己的输入和输出,但是它们之间的关系是"流水线",即上一个命令的输出,能直接当成下一个命令的输入,不用额外操作。

回过头来,既然讲到了CLI,那么有人可能会问:为什么不用API呢?API不也能直接实现么?而且两个API之间的交互甚至比CLI还要方便。

答案很简单,API的调用是要写代码的,既然要写代码,就涉及到编程环境、编译器、运行环境等一系列的东西。拿最简单的Python或者node.js这种语音语言,都要安装对应的环境,然后写代码,最后运行。

当然,有人说,我这安装也就是一次性的事情,这当然也不是不能接受,例如目前最火的openclaw,就是这样干的,替你把环境都配置好,AI直接生成代码就可以开跑了。

既然说到写代码......又带来新的问题......写代码不用Token么?

下图是著名AI公司ScaleKit的官方测试结果:

MCP对比CLI,token的消耗最高几乎可以多几十近百倍。

AI时代,软件的底层操作模式

软件业,归根到底是服务业,几十年以来,所有的软件都是为人设计的,所以从打孔纸带到编译器,然后是命令行,一直到GUI。

这是因为只有人才能使用软件,但是AI时代来了,未来越来越多的软件,第一使用者就变成了AI。

所以,软件的底层思维就变了,未来的软件,首先服务的对象是AI,准确来说,交互逻辑发生了改变。

软件还有没有必要存在?答案当然是有。因为AI要做好一件事,它也需要使用各种工具,只是这些个工具有可能AI自己去创造。

题外话:自举

就像新入行的同学,特别是学习C/C++或者Rust这种系统级语言的同学,特别喜欢问的一个问题:编程语言的编译器是哪里来的?(软件界的先有鸡还是先有蛋)

编程语言要有编译器才能编译成计算机能执行的,但是第一个编译器又是哪里来的呢?

或者说,喜欢争论某某语言是最好的语言的同学,怎么去接受自己语言的编译器是其他语言编写问题?

在计算机领域中,这种情况有一个专用术语,叫做"自举",即一个东西要自己去实现自己。例如C语言的cc编译器,或者CPP的clang/GCC编译器,或者 Rust 编译器 rustc 就是用该语言自身编写的,不再依赖其他语言的编译器。

当然,追根问底,第一个版本的编译器,肯定是用其他语言编写的。但是从第二个版本开始,就用自己写自己的编译器了。

工具的使用是人类与动物的区别

  • 劳动资料的使用和创造,虽然就其萌芽状态来说已为某几种动物所固有,但是这毕竟是人类劳动过程独有的特征,所以富兰克林给人下的定义是「a toolmaking animal」,制造工具的动物。

    ------马克思《资本论》第一卷

从软件领域来看,AI能不能自己给自己造工具呢?当然没问题,但是并没有那么简单,起码目前的AI还做不到。特别是一些复杂的软件工具,如果完全要AI自己去做,还有不少的差距。

所以,软件还有没有必要存在?答案当然是有。因为AI要做一件事,它也需要使用各种工具,只是从未来发展来看,这些个工具有可能AI自己去创造。

正如我以前说的:利用AI进行地图制图,地图制图里面有很多个技术细节,例如边境线的实相交、label沿线标注、要素图斑里面填充符号的边界判断与角度......这些东西,都是在多年的发展中,一步步被总结和完善出来的规则。

这些细节,AI能做么?当然没问题,但是做得最好的永远是GIS软件,AI要做的,并不是全面提掉GIS的功能,而是把GIS工具里面的这些成熟的功能工具,都原子化和agent化,变成AI的手和脚。

当然,AI算力到了一定的程度,它也可以重写重构传统GIS里面的这些工具和功能,不冲突,正所谓:

编程语言有自己的上限,但是编程思想没有。

所以,下一代AI软件化的趋势,才刚刚开始。

而CLI,就是这个开局的一个定式。

如何使用和编写CLI

要使用和编写CLI,首先要了解CLI的基本概念,这样才能写出好用的cli工具来。所以我们先来搞明白两个问题:

一、针对GUI而言,CLI有哪些特点呢?利用AI总结了一下,主要有如下特点:

  1. 纯文本交互,无图形界面

    • 只用键盘输入命令、参数,没有按钮、窗口、图标

    • 输出也是文本:日志、结果、报错、表格等

    • 资源占用极低,适合服务器、无显示器环境

  2. 执行效率高、速度快

    • 启动快、运行快,没有 GUI 渲染开销

    • 适合批量、自动化、高性能任务(搜索、压缩、编译、数据处理)

  3. 易于自动化 & 脚本化

    • 可以写脚本批量执行(bash、python、rust 等)

    • 支持管道、重定向:a | b | c、> log.txt

    • 天然适合 CI/CD、定时任务、批量运维

  4. 功能强大、操作精准

    • 一条命令可以完成复杂操作,参数控制粒度极细

    • 适合开发者、运维、工程师做专业操作

    • 可远程通过 SSH 直接使用,GUI 通常做不到

  5. 跨平台、一致性强

    • Windows/Linux/macOS 都能运行 CLI 工具

    • 行为相对统一,不像 GUI 每个系统差异巨大

    • 易于打包成单文件分发,开箱即用

  6. 学习成本高,记忆门槛大

    • 要记命令、参数、语法,新手不友好

    • 没有提示引导,全靠文档和经验

    • 出错时只返回文本错误,不如 GUI 直观

  7. 资源占用极低

    • 几乎不占内存、CPU、显卡

    • 低配机器、服务器、嵌入式设备都能流畅跑

二、为什么我会推荐要用Rust编写CLI?

(因为我不会C/CPP)

主流的cli都是使用C语言编写的,例如linux的bash、macOS的zsh、windows 的cmd,都是用C写的。还有像git这种使用特别广泛的码农工具,也是C语言写的。

之后一些新一代的cli工具,开始改用其他的一些语言,例如windows的powershell是用C#写的,还有ffmpeg、早期版本的yarn,是用CPP写的。

但是这几年,随着Rust语言的兴起,越来越多的cli工具开始用Rust编写。甚至以前的一些特别知名的软件,也开始用Rust重写,例如:

  • ffmpReg :用Rust重写ffmpeg。

  • uv : ‌u是由 Astral 团队开发的新一代‌Python 包管理与环境管理工具‌,采用 Rust 编写,提供比 pip 快 10-100 倍的依赖解析和安装速度,可替代 pip、pip-tools、virtualenv、poetry 等传统工具。

  • deno : 安全的 Node.js 替代运行时,由Node.js 作者创建,采用 Rust 编写,提供安全、快速、跨平台的 Node.js 运行环境。

  • ruff : Python 代码检查 / 格式化工具,采用 Rust 编写,提供快速、准确的代码检查和格式化功能。

还有大量的linux命令,都在用Rust重写,例如:

  • fd:替代:find的简单好用的文件查找工具

  • bat:替代cat的,是一个带语法高亮、Git 集成的 cat

  • exa / eza:替代:ls的,是一个现代化 ls 工具,支持彩色、图标、树形展示、Git 状态标记 等等等等

为什么大家都开始喜欢用Rust编写CLI了?官方给出来如下理由:

  • 性能与体验双优 Rust 编译为原生机器码,无 GC 开销,启动速度与运行效率接近 C/C++,远胜解释型语言。常见替代工具如 ripgrep(搜索)、fd(文件查找)、bat(带语法高亮的 cat)均由 Rust 编写,性能提升显著。

  • 零内存安全隐患 Rust的借用检查器在编译期阻断野指针、缓冲区溢出等问题,无需垃圾回收即可保证内存安全,让 CLI 工具稳定不崩溃。

  • 分发与部署极简单 二进制文件无依赖,跨平台分发轻松;用户无需配置环境,下载即可用,尤其适合 CI/CD、服务器自动化场景。

  • 可靠的跨平台与错误处理 统一处理跨平台配置、路径与日志,Result/Option 类型让错误提前暴露,友好的编译器提示降低维护成本。

  • 成熟生态加速开发

    • 参数解析:clap 支持子命令、自动帮助文档、参数验证,是 Rust CLI 事实标准。

    • 错误处理:thiserror+anyhow 统一错误类型与信息,调试更高效。

    • 交互体验:indicatif(进度条)、dialoguer(交互式输入)提升用户体验。

    • 工程化:cargo 一站式管理依赖、测试、构建,支持交叉编译,适配多平台。

不过对于我来说,最重要的是以下三点:

  1. 分发与部署极简单,Rust可以写出无依赖独立二进制文件,用户无需配置环境,下载即可用。

  2. 成熟的生态,Rust提供了丰富的库和工具,可以快速开发出功能强大的cli工具。

  3. Rust的错误处理机制,要求你在写代码的时候,就需要考虑到各种可能的错误情况,而不是在运行时才去处理。这样不管是开发的时候,还是AI在调用的时候,都能够有很清晰的反馈。

下面我们给出一个简单的例子,来看看怎么用Rust写一个cli工具。我们写一个读取shapefile文件,并且显示统计数据和进行简单可视化的cli工具。

cli的命令行,主要通过输入参数来进行交互,在Rust里面,可以用过clap包来进行处理:

复制代码
// 定义命令行参数结构
#[derive(Debug, Parser)]
#[command(author, version, about)]
structCli {
/// 输入的 .shp 文件路径
    input: PathBuf,

/// 输出图片路径 (默认: output.png)
#[arg(short, long, default_value = "output.png")]
    output: PathBuf,

/// 输出统计数据 JSON 文件
#[arg(short, long)]
    stats: Option<PathBuf>,

/// 图片宽度
#[arg(long, default_value = "1200")]
    width: i64,

/// 图片高度
#[arg(long, default_value = "800")]
    height: i64,
}

这里面定义了输入路径、输出路径、统计数据路径、图片宽度、图片高度。其中输入shapefile的路径是必填项,其他路径为可选,但是预留了默认值。

定义统计信息:

复制代码
// 定义统计信息结构
#[derive(Debug)]
structShpStats {
    file_name: String,
    feature_count: usize,
    shape_type: String,
    bounds: (f64, f64, f64, f64),
    fields: Vec<String>,
}

然后是编写主函数,来处理命令行参数和业务逻辑:

复制代码
fn main() -> Result<()> {
// 解析命令行参数
let cli = Cli::parse();

// 读取 shp
letmut reader = Reader::from_path(&cli.input)?;
letmut count = 0;
letmut fields = Vec::new();
letmut shp = Vec::new();
for shape_record_result in reader.iter_shapes_and_records() {
match shape_record_result {
Ok((shape, record)) => {
                count += 1;
if count <= 1 {
for (name, _value) in record {
                        fields.push(name);
                    }
                }
                shp.push(shape);
            }
Err(e) => {}
        }
    }

// 范围
let head = reader.header();
let bbox = head.bbox;
let stats = ShpStats {
        file_name: cli.input.file_name().unwrap().to_string_lossy().into(),
        feature_count: shp.len(),
        shape_type: format!("{:?}", head.shape_type),
        bounds: (bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y),
        fields,
    };

println!("✅ 要素数量: {}", stats.feature_count);
println!("✅ 类型: {}", stats.shape_type);
println!("✅ 范围: {:?}", stats.bounds);
println!("✅ 字段: {:?}", stats.fields);

// 输出统计
ifletSome(path) = &cli.stats {
let json = serde_json::to_string_pretty(&stats)?;
        std::fs::write(path, json)?;
    }

// 绘制图片
    render(
        &shp,
        bbox.min.x,
        bbox.min.y,
        bbox.max.x,
        bbox.max.y,
        cli.width,
        cli.height,
        &cli.output,
    )?;

println!("🎉 完成:{}", cli.output.display());
Ok(())
}

// 绘制地图
fnrender(
    shapes: &Vec<Shape>, xmin: f64, ymin: f64,xmax: f64,
    ymax: f64, w: i64, h: i64, output: &PathBuf,
) -> Result<()> {
// 详细实现见源码
    }

执行结果如下:

先看点数据:

可视化效果:

这里渲染的时候我直接绘制了一个5*5像素的矩形,来表示每个POI点的位置,看起来效果不太明显。

在看看面要素:

面要素我仅绘制出来面的边线,没有做填充,如果大家有兴趣,后面有时间我们再专门讲一下绘制填充的实现。

这里都采用的默认参数,我们也可以使用我们自己定义的参数来实现,而clap包已经帮我们提供帮助信息:

例如我们现在要输出json格式的统计数据文件,我们可以这样写:

其他参数也是同理。

总结:

  • AI时代,对CLI以及相应的形式,需求会越来越多,与几十年来的GUI相对应,会成为新的趋势。

  • Rust 提供了编写CLI的全套框架,非常适合CLI的开发。特别是clap包这一类的库,可以用来处理命令行参数,非常方便,可以快速地定义命令行参数,并且可以自动解析参数。

后面我们会通过几篇完整,带代价完整的走一遍CLI编写和AI的交互,有兴趣的同学可以关注一下。

相关推荐
Kurisu_红莉栖2 小时前
c++的复习——多态
开发语言·c++
IT观测2 小时前
轴重检测优选装备 浙江润鑫轴重检测仪稳定可靠
人工智能
秦ぅ时2 小时前
Recraft-V3 技术手册
人工智能·gpt
geovindu2 小时前
go: Prototype Pattern
开发语言·设计模式·golang·原型模式
pearlthriving2 小时前
STL容器及其底层
开发语言·c++·算法
Cosolar2 小时前
文生图竞技场变局:GPT-Image-2 以 1512 分登顶,多模态格局重塑
人工智能·开源·全栈
博.闻广见2 小时前
AI_线性代数-6.PCA降维详解
人工智能·线性代数
互联网江湖2 小时前
苹果翻开AI眼镜的“生死簿”
人工智能
chao1898442 小时前
具有飞行约束的无人机MPC MATLAB实现
开发语言·matlab·无人机