用rust写一个命令行工具(2)

背景

在上一个教程里,教了如何实现一个命令行,以及命令行支持cargo安装。没看的可以参考文章:用rust写一个命令行工具 - 掘金 (juejin.cn)

但是这个命令行工具,在使用的时候有个致命的问题,就是没有命令提示。

比如我要执行my_dev_tool,按tab键我看不到任何命令的提示,这样太不人性化了。

这个文章教会你让自己命令行生成自动补全脚本,让系统shell识别你的命令。

命令补全原理

用clap的生成的命令工具可以通过clap_complete包生成一个补全脚本,把这个脚本加到bash环境就可以自动提示了。

为了生成这些脚本,并让系统能自动提示命令参数,你需要按以下步骤:

1. 修改依赖关系

新增两个依赖clap_completedirs

  1. clap_complete是用来生成补全脚本的。
  2. dirs是用来定位用户目录的。
ini 复制代码
[dependencies]
clap_complete = "4.4.0"
dirs = "4.0"

2. 在你的 Rust 程序中生成补全脚本

首先,你需要修改你的 Rust 程序,使其能够生成相应的补全脚本。这可以在程序的一个特定命令或选项下实现。

1、重构buildCommand方法

把命令构造的内容单独重构为一个方法,后面生成补全命令还需要这个。并添加一个添加bash脚本的命令add-completion

rust 复制代码
use clap::{Command, generate};
use clap_complete::{shells::{Bash, Zsh}, generate_to};
use std::env;
use std::io;
fn build_cli() -> Command {
    Command::new("my_dev_tool")
        .version("1.0")
        .author("tommy <mcg91881127@163.com>")
        .about("Developer's tool for urlencode and time format!")
        .subcommand_required(true)
        .arg_required_else_help(true)
        // 省略。。。。
        .subcommand(Command::new("add-completion")
            .about("Generates completion scripts for your shell"))
}

2. 生成补全脚本

这一步主要是生成补全命令的脚本,bash或者zsh脚本是根据这个脚本进行命令提示的。

rust 复制代码
fn add_completion(matches: &ArgMatches){
    let mut app = build_cli();
    let shell = env::var("SHELL").unwrap_or_default();
    let home_dir = dirs::home_dir().expect("Could not find the home directory");
    let config_file;
    if shell.contains("zsh") {
        config_file = home_dir.join(".zshrc");
        let _ = generate_to(Zsh, &mut app, "my_dev_tool", "./").expect("generate_to failed");
        println!("Generated Zsh completion script.");
    } else {
        config_file = home_dir.join(".bashrc");
        // 默认生成 Bash 补全脚本
        let _ = generate_to(Bash, &mut app, "my_dev_tool", "./").expect("generate_to failed");
        println!("Generated Bash completion script.");
    }
    let completion_script_path = PathBuf::from("./_my_dev_tool");

    let _ = add_completion_to_shell(&config_file, &completion_script_path);
}

在上面的代码中,我们添加了一个名为 add-completions 的子命令,当运行此命令时,程序将生成 Bash 的自动补全脚本。这里需要注意的是:

  1. generate_to(Bash, &mut app, "my_dev_tool", "./")这行代码里的my_dev_tool,是你要补全命令的名字,比如你的工具叫啥这里就填啥。我这个工具命令就是my_dev_tool,所以这里传my_dev_tool
  2. 这里生成的自动补全脚本的名字是_my_dev_tool,这个是默认的命名规则,生成的补全脚本叫"_"+"命令名"。
  3. 为了程序能够区分并生成 .bashrc.zshrc 的补全命令,你需要确定用户正在使用的是哪种 shell。这可以通过检查环境变量 SHELL 来实现,然后根据这个环境变量的值决定修改 .bashrc.zshrc

3. 将生成的补全脚本添加到你的 shell 配置中

生成的补全脚本需要被 source(或等效地添加)到你的 shell 配置文件中(例如 .bashrc, .zshrc 等),这样你的 shell 就能够利用这些脚本进行命令补全。有两种方式可以实现:

#####第一种,手动添加 将以下行添加到 .bashrc.bash_profile,zsh为.zshrc

shell 复制代码
source ~/_my_dev_tool

然后,重新加载配置文件,zsh为source ~/.zshrc

shell 复制代码
source ~/.bashrc

或者重新启动你的终端。

第二种,自动添加到bash或者zsh

然后,使用以下 Rust 代码来自动化添加 shell 配置的过程:

rust 复制代码
use std::env;
use std::fs::{self, OpenOptions};
use std::io::Write;
use std::path::PathBuf;

fn add_completion_to_shell(config_file: &PathBuf, completion_script_path: &PathBuf) -> std::io::Result<()> {
    let completion_script_str = format!("source {}", completion_script_path.display());
    
    let mut config = OpenOptions::new().append(true).open(config_file)?;

    if fs::read_to_string(config_file)?.contains(&completion_script_str) {
        println!("Completion script already added to {}.", config_file.display());
    } else {
        config.write_all(completion_script_str.as_bytes())?;
        println!("Added completion script to {}.", config_file.display());
    }

    Ok(())
}

为了程序能够区分并自动向 .bashrc.zshrc 添加 shell 配置,你需要确定用户正在使用的是哪种 shell。这可以通过检查环境变量 SHELL 来实现,然后根据这个环境变量的值决定修改 .bashrc.zshrc。以下是一个示例实现:

4. source一下命令行或者重启命令行

bash 复制代码
### zsh执行
source ~/.zshrc
### bash执行
source ~/bashrc

展示成果

在命令行输入 my_dev_tool后,按tab键会提示所有命令。

sql 复制代码
~ % my_dev_tool time
add-completion  -- Generates completion scripts for your shell
help            -- Print this message or the help of the given subcommand(s)
timestamp       -- Convert a UNIX timestamp to local datetime
urldecode       -- URL-decode a string
urlencode       -- URL-encode a string
相关推荐
Kapaseker12 分钟前
以为 Rust 快了 50000 倍,结果编译器根本没算
rust
传奇开心果编程36 分钟前
【Xilem 0.4 基础语法学与练】第11课(深入篇):为什么其他 Rust 框架不是一切皆设计图
学习·rust·前端框架
小灰灰搞电子1 小时前
Rust Attribute(属性标记)完整整理
开发语言·后端·rust
tedcloud1232 小时前
God‘s Eye View 怎么搭建?在云服务器上部署一个实时 3D 地球可视化平台
linux·服务器·开发语言·后端·rust
yume_sibai3 小时前
11-Rust 不安全编程(unsafe 关键字 + 裸指针 + FFI + 内联汇编 + 内存安全保证)
汇编·安全·rust
Leaderxin6 小时前
还在用 Electron?6 种跨平台桌面方案横评:Rust + Vue 把安装包从 224MB 干到 4.7MB
rust·vue·跨平台·tauri·桌面应用
海盗12346 小时前
微软技术日报 2026-09-14:Rust 升为微软一级语言,云业务重组为智能体与基础设施
开发语言·microsoft·rust
传奇开心果编程6 小时前
【xilem0.4基础语法学与练】第53课 to_do_mvc 官方示例代码深度解析
学习·rust·前端框架
孙启超6 小时前
【AI开发之Rust】第 5 课:引用与生命周期 —— 借用能活多久?
人工智能·后端·rust·llm·ai应用开发
喵喵锤锤你小可爱7 小时前
SerialHub:把串口变成 WebSocket 字节管道,浏览器和脚本直接读写(开源工具 SerialHub 实战)
websocket·rust·嵌入式·串口调试·开源工具