Course Builder个人学习工作台 · 本地模式
Rust:从所有权到可测试的命令行工具

15|命令行:参数、错误信息与退出状态

目标:让 TextLab 接受恰好一个文件路径,在成功和失败时提供明确的输出与退出状态。

读取函数已经返回 Result,现在入口需要决定用户看到什么。命令行有三个独立通道:标准输出承载结果,标准错误承载诊断,退出状态让脚本判断成功或失败。只打印错误却成功退出,会使自动化误判任务已经完成。

本课程的接口很小:textlab FILE。缺少参数或多余参数是用法错误;文件打不开或不是 UTF-8 是处理错误。路径用 args_os() 获取,保留操作系统字符串,不强制文件名本身是 UTF-8。内容仍要求 UTF-8,这两个约束要区分。

替换入口,不重写计算

从第 14 课检查点继续,完整替换 src/main.rs:

use std::{env, path::PathBuf, process::ExitCode};
use textlab::analyze_file;

fn main() -> ExitCode {
    let mut args = env::args_os().skip(1);
    let path = match args.next() {
        Some(value) => PathBuf::from(value),
        None => {
            eprintln!("usage: textlab FILE");
            return ExitCode::from(2);
        }
    };
    if args.next().is_some() {
        eprintln!("usage: textlab FILE");
        return ExitCode::from(2);
    }

    match analyze_file(&path) {
        Ok(report) => {
            println!("{}", report.summary());
            ExitCode::SUCCESS
        }
        Err(error) => {
            eprintln!("textlab: {}: {error}", path.display());
            ExitCode::from(1)
        }
    }
}

skip(1) 跳过第一个参数(通常是程序路径);next() 取文件参数,再取一次确认没有额外参数。PathBuf 拥有路径,对应借用的 Path,就像 String 与 str 的关系。eprintln! 输出到标准错误,display() 提供面向用户的路径显示。ExitCode::from(2) 与 from(1) 分别是本工具约定的用法失败、处理失败,成功状态为 0。

运行前先预测:Cargo 的选项和 TextLab 的参数,在哪里分开?

cargo run --quiet -- fixtures/sample.txt

预期标准输出:

lines=1 words=2 bytes=12 chars=8

-- 之后的值传给 TextLab。接着运行:

cargo run --quiet --
cargo run --quiet -- fixtures/sample.txt extra
cargo run --quiet -- fixtures/invalid.bin

前两条都应在标准错误输出 usage: textlab FILE,退出状态为 2。最后一条应在标准错误输出以 textlab: fixtures/invalid.bin: 开头的错误,状态为 1;具体编码错误措辞可能随版本变化。这些命令失败是本次实验的预期行为,不应为让终端显示成功而删除错误分支。

引导验证:路径与空内容

  1. 把成功调用的路径换成 fixtures/empty.txt,应输出 lines=0 words=0 bytes=0 chars=0 并成功退出。
  2. 在命令中给路径加双引号:cargo run --quiet -- "fixtures/sample.txt",应与原调用相同。路径含空格时也需要引号保护为一个参数。
  3. 紧接任一失败命令检查退出状态:POSIX shell 使用 echo $?;PowerShell 使用 $LASTEXITCODE。中间不要插入另一条命令,否则读到的是那条命令的状态。

独立练习:让单独的 --help 参数显示用法并成功退出,同时保持 --help extra 仍为用法错误。把下面给出的验收用例先运行一次,确认原程序尚未支持帮助,再修改。

参考修改与验收命令

在现有“拒绝额外参数”的 if 之后、match analyze_file(&path) 之前加入:

if path.as_os_str() == "--help" {
    println!("usage: textlab FILE");
    return ExitCode::SUCCESS;
}

这是依赖现有 main 的局部扩展。as_os_str() 借用路径的操作系统字符串进行比较。先检查额外参数,再处理帮助,保证行为符合题意。

cargo run --quiet -- --help
cargo run --quiet -- --help extra
cargo run --quiet -- fixtures/sample.txt

第一条标准输出为用法、状态为 0;第二条标准错误为用法、状态为 2;第三条保持原报告、状态为 0。帮助功能会占用字面参数 --help;若真有同名文件,用 ./--help 指定路径。练习结束可以保留这个分支,最终验收的核心行为不依赖它。

如果程序把参数误认为 Cargo 选项,检查命令里有没有 --。如果用法错误之后仍继续读文件,检查失败分支是否及时 return。错误消息中的路径是展示用途,不应再拿它反向构造真实路径;实际读取使用原始 PathBuf。

检查点:只替换 src/main.rs,保留库、测试和样本。你现在已经拥有能使用的单文件文本统计工具。

参考资料