Rust 命令行参数
很多适合拿来练手的小项目,比如待办清单、单词计数器,都需要从命令行接收输入,而不是把值写死在源代码里。这一页讲的就是这方面的基础。
用 std::env::args 读取参数
Section titled “用 std::env::args 读取参数”use std::env;
fn main() { let args: Vec<String> = env::args().collect(); println!("{:?}", args);}假如把它编译成一个叫 greet 的程序,然后这样运行:
./greet Alice你会看到:
["greet", "Alice"]第一个元素永远是程序自己的名字,你真正输入的参数从索引 1 开始。
读取某一个参数
Section titled “读取某一个参数”use std::env;
fn main() { let args: Vec<String> = env::args().collect();
if args.len() < 2 { println!("用法:greet <名字>"); return; }
let name = &args[1]; println!("你好,{name}!");}用 cargo run -- Alice 运行,会得到:
你好,Alice!如果不带参数运行,你会看到用法提示,而不是程序崩溃。在用索引取值之前先检查 args.len(),就能避免有人忘了传参数时发生 panic。
把参数解析成数字
Section titled “把参数解析成数字”每个参数进来时都是 String,所以你经常需要用 .parse()(见类型转换一页)把它变成数字:
use std::env;
fn main() { let args: Vec<String> = env::args().collect();
if args.len() < 2 { println!("用法:square <数字>"); return; }
match args[1].parse::<i32>() { Ok(n) => println!("{}", n * n), Err(_) => println!("这不是一个有效的数字"), }}手动处理一个简单的标志
Section titled “手动处理一个简单的标志”标志(flag)是用来打开某个功能的参数,比如 --shout。在用 crate 之前,先看看自己动手怎么处理会很有帮助。让它容易测试的诀窍是:把逻辑放进一个函数,让这个函数以 slice 的形式(&[String])接收参数。这样 main 可以传入真正的参数,而你在试验时可以传入一个手工构造的列表:
fn run(args: &[String]) { let mut shout = false; let mut names = Vec::new();
// 跳过索引 0,也就是程序名 for arg in &args[1..] { if arg == "--shout" { shout = true; } else { names.push(arg); } }
if names.is_empty() { println!("Usage: greet [--shout] <name>..."); return; }
for name in names { let line = format!("Hello, {name}!"); if shout { println!("{}", line.to_uppercase()); } else { println!("{line}"); } }}
fn main() { // 在真正的程序里,你会这样写: // let args: Vec<String> = std::env::args().collect(); // 这里我们手工构造列表,这样在 Playground 里也能运行。 let args = vec![ String::from("greet"), String::from("--shout"), String::from("Ana"), String::from("Ben"), ]; run(&args);}输出:
HELLO, ANA!HELLO, BEN!这里的问候语保留了英文,因为 to_uppercase() 只有作用在拉丁字母上才看得出效果:汉字没有大小写之分。
有几点值得注意:
&args[1..]是除了程序名以外的所有参数。标志可以出现在任何位置:greet Ana --shout Ben也一样能用。- 把
--shout从列表里删掉再运行一次,你会得到普通的问候。再把名字也删掉,就会看到用法提示。 - 因为
run不在乎列表是从哪儿来的,所以换成std::env::args().collect()只需要改main里的一行。
一两个标志这样处理就够了。可一旦你还想要短选项(-s)、标志后面跟值(--count 3)和 --help 帮助信息,手写的循环很快就会膨胀起来。这时就该换用 clap 了。
参数多于两三个时:用 clap
Section titled “参数多于两三个时:用 clap”一旦需要标志(--verbose)、可选值或者好用的 --help 输出,手动对 args 取索引很快就会变得难以维护。标准的解决方案是 clap crate,这样添加:
cargo add clap --features deriveuse clap::Parser;
#[derive(Parser)]struct Args { name: String,
#[arg(short, long, default_value_t = 1)] count: u32,}
fn main() { let args = Args::parse(); for _ in 0..args.count { println!("Hello, {}!", args.name); }}参数解析、校验和一个能用的 --help 帮助信息,全都从结构体定义自动生成。写个临时小脚本不需要 clap,但只要是你会反复使用的工具,它都能帮你省下大量手工活。
Playground 没法传入真正的参数,所以两道练习都从一个手工构造的 Vec<String> 开始,它和 env::args().collect() 返回的结果一模一样。在你自己的电脑上,可以把它换成真正的参数,再试试 cargo run -- ...。
1. 列出参数
Section titled “1. 列出参数”给定 vec,先打印用户输入了几个参数(不算程序名),再从 1 开始按位置逐个打印出来。
提示:参数个数是 args.len() - 1。用 for i in 1..args.len() 循环,就能拿到程序名之后的每个索引。
查看答案
fn main() { // 假装这是 std::env::args().collect() 的结果 let args = vec![ String::from("notes"), String::from("add"), String::from("buy milk"), ];
println!("你传入了 {} 个参数", args.len() - 1); for i in 1..args.len() { println!("{i}: {}", args[i]); }}输出:
你传入了 2 个参数1: add2: buy milk"buy milk" 算作一个参数:在真正的命令行上,你会给它加上引号(cargo run -- add "buy milk"),shell 会把它当作一个完整的字符串传进来。索引 0 依然是程序名,所以循环从 1 开始。
2. 把数字加起来
Section titled “2. 把数字加起来”给定 vec!["sum", "4", "10", "x", "6"],把所有有效数字的参数加起来,遇到不是数字的就打印一条警告。最后打印总和。
提示:遍历 &args[1..],用 match arg.parse::<i32>(),就像把参数解析成数字里那样。在循环外面先写好 let mut total = 0;。
查看答案
fn main() { let args = vec![ String::from("sum"), String::from("4"), String::from("10"), String::from("x"), String::from("6"), ];
let mut total = 0; for arg in &args[1..] { match arg.parse::<i32>() { Ok(n) => total += n, Err(_) => println!("跳过“{arg}”:不是数字"), } } println!("合计:{total}");}输出:
跳过“x”:不是数字合计:20每个参数进来时都是文本,就连 "4" 也是,所以每一个都得解析。遇到错误输入时打印一条消息再跳过,比直接崩溃友好得多。如果你的程序更适合在第一个错误处停下,也可以用 return。
std::env::args().collect()会给你一个装着命令行参数的Vec<String>,索引 0 永远是程序名。- 用索引取值前先检查
args.len(),免得缺少参数时程序 panic。 .parse()把String类型的参数变成数字,和别处的用法一样。- 把参数处理放进一个接收
&[String]的函数里,这样就能用手工构造的列表来测试。 - 只要参数比两三个普通参数更复杂,
clapcrate 就是公认的、久经考验的标准选择。
小测验
1. 调用 env::args().collect() 后,得到的 Vec 的第一个元素是什么?
2. 为什么在对 args 取索引前要先检查 args.len()?
3. 和手动对 args 取索引相比,clap crate 能多给你什么?
得分: 0 / 3
- 接受命令行参数 —《Rust 程序设计语言》:官方教程里
minigrep项目的开头,从命令行读取要搜索的词和文件名。 std::env::args— 标准库文档(英文):args()到底返回什么,以及什么时候会 panic(参数不是有效的 Unicode 时)。- 程序参数 — 通过例子学 Rust:一个简短的示例,后面还有一篇用
match处理参数的参数解析。 - Command Line Applications in Rust(英文):一本免费的小书,讲怎样做出真正的命令行工具,从解析参数到测试和发布。
- clap derive tutorial — docs.rs(英文):一步步讲解如何用
clap处理标志、选项、子命令和校验。