Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

12.8 将错误信息写入到标准错误

12.8.0. 写在正文之前

第12章要做一个实例的项目——一个命令行程序。这个程序是一个grep(Global Regular Expression Print),是一个全局正则搜索和输出的工具。它的功能是在指定的文件中搜索出指定的文字。

这个项目分为这么几步:

12.8.1. 回顾

以下是截止到上一篇文章为止所写出的全部代码。

lib.rs:

#![allow(unused)]
fn main() {
use std::error::Error;
use std::fs;

pub struct Config {
    pub query: String,
    pub filename: String,
    pub case_sensitive: bool,
}

impl Config {
    pub fn new(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("not enough arguments");
        }
        let query = args[1].clone();
        let filename = args[2].clone();
        let case_sensitive = std::env::var("IGNORE_CASE").is_err();
        Ok(Config {
            query,
            filename,
            case_sensitive,
        })
    }
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let contents = fs::read_to_string(config.filename)?;
    let results = if config.case_sensitive {
        search(&config.query, &contents)
    } else {
        search_case_insensitive(&config.query, &contents)
    };
    for line in results {
        println!("{}", line);
    }
    Ok(())
}

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let mut results = Vec::new();
    for line in contents.lines() {
        if line.contains(query) {
            results.push(line);
        }
    }
    results
}

pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let mut results = Vec::new();
    let query = query.to_lowercase();
    for line in contents.lines() {
        if line.to_lowercase().contains(&query) {
            results.push(line);
        }
    }
    results
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn case_sensitive() {
        let query = "duct";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";

        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
    }

    #[test]
    fn case_insensitive() {
        let query = "rUsT";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";

        assert_eq!(
            vec!["Rust:", "Trust me."],
            search_case_insensitive(query, contents)
        );
    }
}
}

main.rs:

use std::env;
use std::process;
use minigrep::Config;

fn main() {
    let args: Vec<String> = env::args().collect();
    let config = Config::new(&args).unwrap_or_else(|err| {
        println!("Problem parsing arguments: {}", err);
        process::exit(1);
    });
    if let Err(e) = minigrep::run(config) {
        println!("Application error: {}", e);
        process::exit(1);
    }
}

12.8.2. 标准输出 vs. 标准错误

这个代码目前把所有的信息(包括错误信息)都打印到终端上了,而大多数的终端它都提供两种输出:标准输出(stdout)标准错误(stderr)

一般的信息应该输出到标准输出里,而错误信息应该输出到标准错误里。 这种区分的好处在于使正常的输出重定向到文件里面,而错误信息可以在屏幕上打印。

println!这个宏只能将信息打印到标准输出里。而eprintln!这个宏可以把信息输出到标准错误里。

我们使用目前的代码,在终端中执行这个命令:

cargo run > output.txt

也就是把输出重定向到output.txt里,但是这个指令没有带参数,也就是说程序应该会报错,但由于我们把错误信息也写在标准输出里,所以说报错的信息被写在output.txt里了。

更好的做法是将错误信息打印到标准错误里,这样就可以让标准输出的内容保持整洁,不与报错信息混在一起。

12.8.3. 修改代码

将错误信息打印到标准错误里的代码修改还算简单,我们只需要把所有的报错信息从用println!打印改为用eprintln!打印即可。因为所有的报错处理都在main.rs里,所以我们只需要对main.rs稍作修改即可,lib.rs就完全不需要修改:

use std::env;
use std::process;
use minigrep::Config;

fn main() {
    let args: Vec<String> = env::args().collect();
    let config = Config::new(&args).unwrap_or_else(|err| {
        eprintln!("Problem parsing arguments: {}", err);
        process::exit(1);
    });
    if let Err(e) = minigrep::run(config) {
        eprintln!("Application error: {}", e);
        process::exit(1);
    }
}

然后我们再执行之前那个命令。那个指令没有带参数,所以程序会报错,但这次它不会把报错信息放在output.txt里,而是会直接在终端中打印出来:

$ cargo run > output.txt
Problem parsing arguments: not enough arguments

然后再试一下正常带参数的:

$ cargo run -- to poem.txt > output.txt

输出内容被重定向到output.txt里了,打开它:

Are you nobody, too?
How dreary to be somebody!

这就是我们想要的结果:错误直接在终端打印,而正常输出在重定向的文件里。