将错误重定向到标准错误
Redirecting Errors to Standard Error
目前,我们使用 println! 宏将所有输出都写入终端。在大多数终端中,有两种输出:用于一般信息的 标准输出(stdout)和用于错误信息的 标准错误(stderr)。这种区别使用户可以选择将程序的成功输出定向到文件,但仍然将错误信息打印到屏幕上。
At the moment, we’re writing all of our output to the terminal using the
println! macro. In most terminals, there are two kinds of output: standard
output (stdout) for general information and standard error (stderr) for
error messages. This distinction enables users to choose to direct the
successful output of a program to a file but still print error messages to the
screen.
println! 宏只能打印到标准输出,因此我们必须使用其他工具来打印到标准错误。
The println! macro is only capable of printing to standard output, so we have
to use something else to print to standard error.
检查错误被写入何处
Checking Where Errors Are Written
首先,让我们观察 minigrep 目前打印的内容是如何写入标准输出的,包括我们希望改为写入标准错误的任何错误信息。我们将通过在故意引发错误的同时将标准输出流重定向到文件来实现这一点。我们不会重定向标准错误流,因此任何发送到标准错误的内容将继续显示在屏幕上。
First, let’s observe how the content printed by minigrep is currently being
written to standard output, including any error messages we want to write to
standard error instead. We’ll do that by redirecting the standard output stream
to a file while intentionally causing an error. We won’t redirect the standard
error stream, so any content sent to standard error will continue to display on
the screen.
命令行程序应该将错误信息发送到标准错误流,这样即使我们将标准输出流重定向到文件,我们仍然可以在屏幕上看到错误信息。我们的程序目前表现得并不好:我们将看到它把错误信息输出保存到了文件中!
Command line programs are expected to send error messages to the standard error stream so that we can still see error messages on the screen even if we redirect the standard output stream to a file. Our program is not currently well behaved: We’re about to see that it saves the error message output to a file instead!
为了演示这种行为,我们将使用 > 和我们想要重定向标准输出流的文件路径 output.txt 来运行程序。我们不传递任何参数,这应该会导致一个错误:
To demonstrate this behavior, we’ll run the program with > and the file path,
output.txt, that we want to redirect the standard output stream to. We won’t
pass any arguments, which should cause an error:
$ cargo run > output.txt
> 语法告诉 shell 将标准输出的内容写入 output.txt 而不是屏幕。我们没有看到预期的错误信息打印到屏幕上,所以这意味着它肯定进入了文件中。这是 output.txt 包含的内容:
The > syntax tells the shell to write the contents of standard output to
output.txt instead of the screen. We didn’t see the error message we were
expecting printed to the screen, so that means it must have ended up in the
file. This is what output.txt contains:
Problem parsing arguments: not enough arguments
是的,我们的错误信息正在被打印到标准输出。对于这样的错误信息,将其打印到标准错误要有用得多,这样只有成功运行的数据才会最终出现在文件中。我们将改变这一点。
Yup, our error message is being printed to standard output. It’s much more useful for error messages like this to be printed to standard error so that only data from a successful run ends up in the file. We’ll change that.
将错误打印到标准错误
Printing Errors to Standard Error
我们将使用示例 12-24 中的代码来更改打印错误信息的方式。得益于我们在本章前面进行的重构,所有打印错误信息的代码都在一个函数 main 中。标准库提供了打印到标准错误流的 eprintln! 宏,因此让我们将两处调用 println! 打印错误的地方改为使用 eprintln!。
We’ll use the code in Listing 12-24 to change how error messages are printed.
Because of the refactoring we did earlier in this chapter, all the code that
prints error messages is in one function, main. The standard library provides
the eprintln! macro that prints to the standard error stream, so let’s change
the two places we were calling println! to print errors to use eprintln!
instead.
use std::env;
use std::error::Error;
use std::fs;
use std::process;
use minigrep::{search, search_case_insensitive};
fn main() {
let args: Vec<String> = env::args().collect();
let config = Config::build(&args).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {err}");
process::exit(1);
});
if let Err(e) = run(config) {
eprintln!("Application error: {e}");
process::exit(1);
}
}
pub struct Config {
pub query: String,
pub file_path: String,
pub ignore_case: bool,
}
impl Config {
fn build(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}
let query = args[1].clone();
let file_path = args[2].clone();
let ignore_case = env::var("IGNORE_CASE").is_ok();
Ok(Config {
query,
file_path,
ignore_case,
})
}
}
fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.file_path)?;
let results = if config.ignore_case {
search_case_insensitive(&config.query, &contents)
} else {
search(&config.query, &contents)
};
for line in results {
println!("{line}");
}
Ok(())
}
现在让我们以同样的方式再次运行程序,不带任何参数并使用 > 重定向标准输出:
Let’s now run the program again in the same way, without any arguments and
redirecting standard output with >:
$ cargo run > output.txt
Problem parsing arguments: not enough arguments
现在我们看到了屏幕上的错误,而 output.txt 没有任何内容,这正是我们对命令行程序所期望的行为。
Now we see the error onscreen and output.txt contains nothing, which is the behavior we expect of command line programs.
让我们再次运行程序,使用不会导致错误但仍然将标准输出重定向到文件的参数,如下所示:
Let’s run the program again with arguments that don’t cause an error but still redirect standard output to a file, like so:
$ cargo run -- to poem.txt > output.txt
我们不会在终端看到任何输出,而 output.txt 将包含我们的结果:
We won’t see any output to the terminal, and output.txt will contain our results:
文件名:output.txt Filename: output.txt
Are you nobody, too?
How dreary to be somebody!
这证明了我们现在在适当时将标准输出用于成功输出,将标准错误用于错误输出。
This demonstrates that we’re now using standard output for successful output and standard error for error output as appropriate.
总结
Summary
本章回顾了你目前学到的一些主要概念,并介绍了如何在 Rust 中执行常见的 I/O 操作。通过使用命令行参数、文件、环境变量以及用于打印错误的 eprintln! 宏,你现在已经准备好编写命令行应用程序了。结合前面章节的概念,你的代码将组织良好,能够有效地将数据存储在适当的数据结构中,优雅地处理错误,并且测试充分。
This chapter recapped some of the major concepts you’ve learned so far and
covered how to perform common I/O operations in Rust. By using command line
arguments, files, environment variables, and the eprintln! macro for printing
errors, you’re now prepared to write command line applications. Combined with
the concepts in previous chapters, your code will be well organized, store data
effectively in the appropriate data structures, handle errors nicely, and be
well tested.
接下来,我们将探索一些受函数式语言影响的 Rust 特性:闭包(closures)和迭代器(iterators)。
Next, we’ll explore some Rust features that were influenced by functional languages: closures and iterators.