trpl-zh-cn/src/ch12-01-accepting-command-line-arguments.md

135 lines
6.7 KiB
Markdown
Raw Normal View History

2017-03-02 23:36:03 +08:00
## 接受命令行参数
2017-03-04 12:10:32 +08:00
> [ch12-01-accepting-command-line-arguments.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch12-01-accepting-command-line-arguments.md)
2017-03-02 23:36:03 +08:00
> <br>
2017-08-25 16:36:49 +08:00
> commit 50658e654fb6a9208b635179cdd79939aa0ab133
2017-03-02 23:36:03 +08:00
2017-08-25 16:36:49 +08:00
一如之前使用 `cargo new` 新建一个项目。我们称之为 `minigrep` 以便与可能已经安装在系统上的`grep`工具相区别:
2017-03-02 23:36:03 +08:00
2017-08-25 16:36:49 +08:00
```text
$ cargo new --bin minigrep
Created binary (application) `minigrep` project
$ cd minigrep
2017-04-05 23:13:49 +08:00
```
2017-08-25 16:36:49 +08:00
第一个任务是让 `minigrep` 能够接受两个命令行参数:文件名和要搜索的字符串。也就是说我们希望能够使用 `cargo run`、要搜索的字符串和被搜索的文件的路径来运行程序,像这样:
```text
2017-04-05 23:13:49 +08:00
$ cargo run searchstring example-filename.txt
```
2017-08-25 16:36:49 +08:00
现在 `cargo new` 生成的程序忽略任何传递给它的参数。crates.io 上有一些现成的库可以帮助我们接受命令行参数,不过因为正在学习,让我们自己来实现一个。
2017-04-05 23:13:49 +08:00
### 读取参数值
为了能够获取传递给程序的命令行参数的值,我们需要调用一个 Rust 标准库提供的函数:`std::env::args`。这个函数返回一个传递给程序的命令行参数的**迭代器***iterator*)。我们还未讨论到迭代器,第十三章会全面的介绍他们。但是对于我们现在的目的来说只需要明白两点:
2017-03-02 23:36:03 +08:00
1. 迭代器生成一系列的值。
2. 在迭代器上调用`collect`方法可以将其生成的元素转换为一个 vector。
2017-04-05 23:13:49 +08:00
让我们尝试一下:使用列表 12-1 中的代码来读取任何传递给`greprs`的命令行参数并将其收集到一个 vector 中。
<!-- Give what a try, here, what are we making? Can you lay that out? I've
tried above but I'm not sure it's complete -->
<!-- We're not creating anything, we're just reading. I'm not sure if I've made
this clearer. /Carol -->
2017-03-02 23:36:03 +08:00
<span class="filename">Filename: src/main.rs</span>
```rust
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
println!("{:?}", args);
}
```
2017-04-05 23:13:49 +08:00
Listing 12-1: Collect the command line arguments into a vector and print them
out
2017-03-02 23:36:03 +08:00
<!-- Will add wingdings in libreoffice /Carol -->
2017-04-05 23:13:49 +08:00
首先使用`use`语句来将`std::env`模块引入作用域以便可以使用它的`args`函数。注意`std::env::args`函数嵌套进了两层模块中。如第七章讲到的,当所需函数嵌套了多于一层模块时,通常将父模块引入作用域,而不是其自身。这便于我们利用`std::env`中的其他函数。这比增加了`use std::env::args;`后仅仅使用`args`调用函数要更明确一些;这样看起来好像一个定义于当前模块的函数。
<!-- We realized that we need to add the following caveat to fully specify
the behavior of `std::env::args` /Carol -->
<!-- PROD: START BOX -->
> 注意:`std::env::args`在其任何参数包含无效 Unicode 字符时会 panic。如果你需要接受包含无效 Unicode 字符的参数,使用`std::env::args_os`代替。这个函数返回`OsString`值而不是`String`值。出于简单考虑这里使用`std::env::args`,因为`OsString`值每个平台都不一样而且比`String`值处理起来更复杂。
<!-- PROD: END BOX -->
2017-03-02 23:36:03 +08:00
2017-04-05 23:13:49 +08:00
<!--what is it we're making into a vector here, the arguments we pass?-->
<!-- The iterator of the arguments. /Carol -->
在`main`函数的第一行,我们调用了`env::args`,并立即使用`collect`来创建了一个包含迭代器所有值的 vector。`collect`可以被用来创建很多类型的集合,所以这里显式注明的`args`类型来指定我们需要一个字符串 vector。虽然在 Rust 中我们很少会需要注明类型,`collect`就是一个经常需要注明类型的函数,因为 Rust 不能推断出你想要什么类型的集合。
2017-03-02 23:36:03 +08:00
最后,我们使用调试格式`:?`打印出 vector。让我们尝试不用参数运行代码接着用两个参数
```
$ cargo run
["target/debug/greprs"]
$ cargo run needle haystack
...snip...
["target/debug/greprs", "needle", "haystack"]
```
2017-04-05 23:13:49 +08:00
<!--Below --- This initially confused me, do you mean that the argument at
index 0 is taken up by the name of the binary, so we start arguments at 1 when
setting them? It seems like it's something like that, reading on, and I've
edited as such, can you check? -->
<!-- Mentioning the indexes here seemed repetitive with the text after Listing
12-2. We're not "setting" arguments here, we're saving the value in variables.
I've hopefully cleared this up without needing to introduce repetition.
/Carol-->
你可能注意到了 vector 的第一个值是"target/debug/greprs",它是我们二进制文件的名称。其原因超出了本章介绍的范围,不过需要记住的是我们保存了所需的两个参数。
2017-04-05 23:13:49 +08:00
### 将参数值保存进变量
2017-03-04 12:10:32 +08:00
2017-04-05 23:13:49 +08:00
打印出参数 vector 中的值仅仅展示了可以访问程序中指定为命令行参数的值。但是这并不是我们想要做的,我们希望将这两个参数的值保存进变量这样就可以在程序使用这些值。让我们如列表 12-2 这样做:
<!-- By 'find the ones we care about' did you mean set particular arguments so
the user knows what to enter? I'm a little confused about what we are doing,
I've tried to clarify above -->
<!-- We're incrementally adding features and adding some code that helps the
reader be able to see and experience what the code is doing rather than just
taking our word for it. I've hopefully clarified below. /Carol -->
2017-03-04 12:10:32 +08:00
<span class="filename">Filename: src/main.rs</span>
2017-04-05 23:13:49 +08:00
```rust,should_panic
2017-03-04 12:10:32 +08:00
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
2017-04-05 23:13:49 +08:00
let query = &args[1];
2017-03-04 12:10:32 +08:00
let filename = &args[2];
2017-04-05 23:13:49 +08:00
println!("Searching for {}", query);
2017-03-04 12:10:32 +08:00
println!("In file {}", filename);
}
```
2017-04-05 23:13:49 +08:00
Listing 12-2: Create variables to hold the query argument and filename argument
2017-03-04 12:10:32 +08:00
<!-- Will add ghosting and wingdings in libreoffice /Carol -->
2017-04-05 23:13:49 +08:00
正如我们在打印出 vector 时所看到的,程序的名称占据了 vector 的第一个值`args[0]`,所以我们从索引`1`开始。第一个参数`greprs`是需要搜索的字符串,所以将其将第一个参数的引用存放在变量`query`中。第二个参数将是文件名,所以将第二个参数的引用放入变量`filename`中。
我们将临时打印出出这些变量的值,再一次证明代码如我们期望的那样工作。让我们使用参数`test`和`sample.txt`再次运行这个程序:
2017-03-05 00:41:41 +08:00
```
$ cargo run test sample.txt
Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
2017-04-05 23:13:49 +08:00
Running `target/debug/greprs test sample.txt`
2017-03-05 00:41:41 +08:00
Searching for test
In file sample.txt
```
2017-04-05 23:13:49 +08:00
好的,它可以工作!我们将所需的参数值保存进了对应的变量中。之后会增加一些错误处理来应对类似用户没有提供参数的情况,不过现在我们将忽略他们并开始增加读取文件功能。