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

Przekierowywanie błędów do standardowego strumienia błędów

W tej chwili wszystkie nasze dane wyjściowe zapisujemy do terminala za pomocą makra println!. W większości terminali istnieją dwa rodzaje danych wyjściowych: standardowe wyjście (stdout) dla ogólnych informacji i standardowy błąd (stderr) dla komunikatów o błędach. To rozróżnienie umożliwia użytkownikom skierowanie pomyślnych danych wyjściowych programu do pliku, ale nadal wyświetlanie komunikatów o błędach na ekranie.

Makro println! jest w stanie drukować tylko na standardowe wyjście, więc musimy użyć czegoś innego do drukowania na standardowy błąd.

Sprawdzanie, gdzie są zapisywane błędy

Najpierw zaobserwujmy, jak zawartość drukowana przez minigrep jest obecnie zapisywana na standardowe wyjście, w tym wszelkie komunikaty o błędach, które chcielibyśmy zamiast tego zapisać na standardowy strumień błędów. Zrobimy to, przekierowując standardowy strumień wyjściowy do pliku, celowo powodując błąd. Nie będziemy przekierowywać standardowego strumienia błędów, więc wszelka zawartość wysłana na standardowy strumień błędów będzie nadal wyświetlana na ekranie.

Oczekuje się, że programy wiersza poleceń wysyłają komunikaty o błędach do standardowego strumienia błędów, abyśmy mogli nadal widzieć komunikaty o błędach na ekranie, nawet jeśli przekierujemy standardowy strumień wyjściowy do pliku. Nasz program nie zachowuje się obecnie poprawnie: zaraz zobaczymy, że zapisuje komunikaty o błędach do pliku!

Aby zademonstrować to zachowanie, uruchomimy program z > i ścieżką pliku output.txt, do której chcemy przekierować standardowy strumień wyjściowy. Nie będziemy przekazywać żadnych argumentów, co powinno spowodować błąd:

$ cargo run > output.txt

Składnia > informuje powłokę, aby zapisała zawartość standardowego wyjścia do output.txt zamiast na ekran. Nie zobaczyliśmy komunikatu o błędzie, którego się spodziewaliśmy, na ekranie, więc musi on trafić do pliku. Oto co zawiera output.txt:

Problem parsing arguments: not enough arguments

Tak, nasz komunikat o błędzie jest drukowany na standardowe wyjście. Dużo bardziej użyteczne jest drukowanie takich komunikatów o błędach na standardowy strumień błędów, tak aby do pliku trafiały tylko dane z pomyślnego uruchomienia. Zmienimy to.

Drukowanie błędów na standardowy strumień błędów

Użyjemy kodu z Listing 12-24, aby zmienić sposób drukowania komunikatów o błędach. Dzięki refaktoryzacji, którą przeprowadziliśmy wcześniej w tym rozdziale, cały kod drukujący komunikaty o błędach znajduje się w jednej funkcji, main. Standardowa biblioteka dostarcza makro eprintln!, które drukuje na standardowy strumień błędów, więc zmieńmy dwa miejsca, w których wywoływaliśmy println! do drukowania błędów, aby zamiast tego użyć eprintln!.

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(())
}

Uruchommy teraz program ponownie w ten sam sposób, bez żadnych argumentów i przekierowując standardowe wyjście za pomocą >:

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

Teraz widzimy błąd na ekranie, a plik output.txt jest pusty, co jest zachowaniem, którego oczekujemy od programów wiersza poleceń.

Uruchommy program ponownie z argumentami, które nie powodują błędu, ale nadal przekierowują standardowe wyjście do pliku, w następujący sposób:

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

Nie zobaczymy żadnych danych wyjściowych w terminalu, a plik output.txt będzie zawierał nasze wyniki:

Nazwa pliku: output.txt

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

To pokazuje, że teraz używamy standardowego wyjścia dla pomyślnych wyników i standardowego strumienia błędów dla komunikatów o błędach, stosownie do potrzeb.

Podsumowanie

Ten rozdział podsumował niektóre z głównych pojęć, które poznałeś do tej pory, i omówił, jak wykonywać typowe operacje wejścia/wyjścia w Rust. Korzystając z argumentów wiersza poleceń, plików, zmiennych środowiskowych i makra eprintln! do drukowania błędów, jesteś teraz przygotowany do pisania aplikacji wiersza poleceń. W połączeniu z koncepcjami z poprzednich rozdziałów, Twój kod będzie dobrze zorganizowany, skutecznie przechowywał dane w odpowiednich strukturach danych, ładnie obsługiwał błędy i będzie dobrze przetestowany.

Następnie zbadamy niektóre funkcje Rusta, które zostały zainspirowane językami funkcyjnymi: domknięcia i iteratory.