如何在Rust中访问命令行参数?

196

Rust教程没有解释如何从命令行获取参数。在所有示例中,fn main()都只显示为空参数列表。

main访问命令行参数的正确方法是什么?

13个回答

218
你可以通过使用std::env::argsstd::env::args_os函数来访问命令行参数。这两个函数都返回一个参数的迭代器。前者迭代的是String(易于处理),但如果其中一个参数不是有效的Unicode,则会出现错误。后者迭代的是OsString,并且永远不会出错。
请注意,迭代器的第一个元素是程序本身的名称(这是所有主要操作系统的约定),因此第一个参数实际上是第二个迭代的元素。
处理args的结果的一种简单方法是将其转换为Vec
use std::env;

fn main() {
    let args: Vec<_> = env::args().collect();
    if args.len() > 1 {
        println!("The first argument is {}", args[1]);
    }
}

你可以使用整个标准迭代工具箱来处理这些参数。例如,只需获取第一个参数:
use std::env;

fn main() {
    // we use nth(1) for the first argument because
    // nth(0) traditionally corresponds to the binary path
    if let Some(arg1) = env::args().nth(1) {
        println!("The first argument is {}", arg1);
    }
}

你可以在crates.io上找到用于解析命令行参数的库:
  • clap:使用流畅的API描述要解析的选项。比docopt更快,且提供更多控制。
  • getopts:流行C库的移植版本。更低级别,提供更多控制。

29

Docopt同样适用于Rust,它可以根据使用字符串为您生成解析器。作为Rust的额外奖励,可以使用宏自动生成结构体并进行基于类型的解码:

docopt!(Args, "
Usage: cp [-a] SOURCE DEST
       cp [-a] SOURCE... DIR

Options:
    -a, --archive  Copy everything.
")

你可以使用以下代码获取参数:

let args: Args = Args::docopt().decode().unwrap_or_else(|e| e.exit());

阅读README和文档可获得许多完整的工作示例。

免责声明:我是这个库的作者之一。


docopt已经很长时间没有维护了。https://github.com/docopt/docopt.rs - Sedat Kapanoglu

12

Rust在getopts箱中具有类似于getopt的CLI参数解析。


10

对我来说,getopts 总是感觉太底层了,而 docopt.rs 又有太多的魔法。

我想要的是一些明确而简单的东西,但如果需要的话仍然提供所有功能。

这就是 clap-rs 的用处所在。
它有点像 Python 中的 argparse。 以下是一个示例:

let matches = App::new("myapp")
                      .version("1.0")
                      .author("Kevin K. <kbknapp@gmail.com>")
                      .about("Does awesome things")
                      .arg(Arg::with_name("CONFIG")
                           .short("c")
                           .long("config")
                           .help("Sets a custom config file")
                           .takes_value(true))
                      .arg(Arg::with_name("INPUT")
                           .help("Sets the input file to use")
                           .required(true)
                           .index(1))
                      .arg(Arg::with_name("debug")
                           .short("d")
                           .multiple(true)
                           .help("Sets the level of debugging information"))
                      .get_matches();
你可以这样访问你的参数:

You can access your parameters like so:

println!("Using input file: {}", matches.value_of("INPUT").unwrap());

// Gets a value for config if supplied by user, or defaults to "default.conf"
let config = matches.value_of("CONFIG").unwrap_or("default.conf");
println!("Value for config: {}", config);

(从官方文档复制)


1
我喜欢 clap-rs 让你可以在 yaml 文件中定义接口。此外,它生成的使用说明非常漂亮。 - Chuck Wooters
这帮助我快速设置了我的CLI应用程序。谢谢! - dimitarvp
这还是最新的吗?文档中的API看起来已经不同了。 - Lennart
可能吧。我推荐在新项目中使用 clap-derive。它更加灵活多样,而且看起来更加美观。https://docs.rs/clap/latest/clap/_derive/_tutorial - undefined

8

在较小的项目中,如果没有必要,我个人不喜欢使用库,而在Rust中解析命令行参数非常简单:

fn main() {
    let config: String;

    let mut args = env::args().skip(1);
    while let Some(arg) = args.next() {
        match &arg[..] {
            "-h" | "--help" => help(),
            "--version" => {
                println!("{} {}", prog().unwrap_or_default(), VERSION);
            }
            "-q" | "--quiet" => {
                println!("Quiet mode is not supported yet.");
            }
            "-v" | "--verbose" => {
                println!("Verbose mode is not supported yet.");
            }
            "-c" | "--config" => {
                if let Some(arg_config) = args.next() {
                    config = arg_config;
                } else {
                    panic!("No value specified for parameter --config.");
                }
            }
            _ => {
                if arg.starts_with('-') {
                    println!("Unkown argument {}", arg);
                } else {
                    println!("Unkown positional argument {}", arg);
                }
            }
        }
    }
}

为了处理非字符串参数,您可以使用String::parse()方法尝试将给定的字符串解析为指定的类型:
"-s" | "--size" => {
    if let Some(arg_size) = arg_it.next() {
        size = arg_size
            .parse::<usize>()
            .expect("Size argument expects an integer value.");
    } else {
        panic!("No value specified for parameter size.");
    }
}

如果您想使用等号(=)而不是空格处理值,可以在 match 语句之前包含以下代码,并匹配键值:

let (key, value) = match arg.contains('=') {
    true => {
        let str_vec: Vec<&str> = arg.split('=').collect();
        (String::from(str_vec[0]), Some(String::from(str_vec[1])))
    },
    false => {
        (arg, None)
    }
};

非常出色的回答! - undefined

5
从版本0.8/0.9开始,函数args()的正确路径应为::std::os::args,即:
fn main() {
  let args: ~[~str] = ::std::os::args();
  println(args[0]);
}

似乎Rust在标准I/O方面仍然比较不稳定,因此这篇文章可能很快就过时了。

感谢更新!在1.0发布之后,我想我会重新考虑接受的答案。 - shutefan

4

同时也请查看structopt:

extern crate structopt;
#[macro_use]
extern crate structopt_derive;

use structopt::StructOpt;

#[derive(StructOpt, Debug)]
#[structopt(name = "example", about = "An example of StructOpt usage.")]
struct Opt {
    /// A flag, true if used in the command line.
    #[structopt(short = "d", long = "debug", help = "Activate debug mode")]
    debug: bool,

    /// An argument of type float, with a default value.
    #[structopt(short = "s", long = "speed", help = "Set speed", default_value = "42")]
    speed: f64,

    /// Needed parameter, the first on the command line.
    #[structopt(help = "Input file")]
    input: String,

    /// An optional parameter, will be `None` if not present on the
    /// command line.
    #[structopt(help = "Output file, stdout if not present")]
    output: Option<String>,
}

fn main() {
    let opt = Opt::from_args();
    println!("{:?}", opt);
}

https://github.com/TeXitoi/structopt


1
现在,structopt 也可以通过 clap 使用了。 - Caesar

3
Rust又有了新的变化。现在已不再使用os::args(),而是使用std::args()。但是std::args()并不是数组,它返回的是一个迭代器。你可以迭代处理命令行参数,但不能使用下标来访问它们。
如果你想将命令行参数作为字符串向量来处理,现在可以这么做: http://doc.rust-lang.org/std/env/fn.args.html
use std::env;
...
let args: Vec<String> = env::args().map(|s| s.into_string().unwrap()).collect();

Rust - 学会拥抱变化的痛苦。

(Rust是一种编程语言,其特点是内存安全和高性能)

10
现在你只需要执行 env::args().collect() - tshepang

3

《Rust书籍》中的“无标准库”章节介绍了如何访问命令行参数(另一种方式)。

// Entry point for this program
#[start]
fn start(_argc: isize, _argv: *const *const u8) -> isize {
    0
}

现在,这个示例也有 #![no_std]。我认为这意味着通常std库会为您的二进制文件提供真正的入口点并调用一个名为main()的全局函数。另一种选择是使用#![no_main]来“禁用main桥接”。如果我没记错的话,就是告诉编译器你要完全控制程序的启动方式。
#![no_std]
#![no_main]

#[no_mangle] // ensure that this symbol is called `main` in the output
pub extern fn main(argc: isize, argv: *const *const u8) -> isize {
    0
}

如果你只是想读取命令行参数,我不认为这是一种“好”的做法。其他答案中提到的std::os 模块似乎是一种更好的方式。我发表这个答案是为了完整性。


太神奇了。在阅读文档和搜索结果大约半个小时后,是我找到的第一种获取实际参数字节的方法,而无需对其进行解码以获得有效的Unicode。我不知道Rust作为一种语言如何能够在没有更好的处理“我收到了无效的Unicode作为命令行参数,而且我没有责任质疑它是否有效,我的工作是将其传递给其他程序/库”的边缘情况的方式下走到这一步。叹气 +1 - undefined
那本关于 Rust 的书链接已经失效了,顺便说一下,没有标准库的章节。 - undefined

2

如果您想要迭代命令行参数:

use std::env::{args,Args};

fn main() {
    let mut args:Args=args();
    // args.nth(0) cannot borrow as mutable. that is why  let mut args
    // nth return an element of iterator
    let first=args.nth(1).unwrap();
    // iterator has next method. we want to get the first element of next iterator. NOT args.nth(2)
    // chars returns an iterator
    let operator=args.nth(0).unwrap().chars().next().unwrap();
    let second=args.nth(0).unwrap();
}

如果你想直接访问第n个参数:

fn get_nth_arg(n:usize)->String{
    std::env::args().nth(n).unwrap()
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接