Перенаправлення помилок до стандартного потоку помилок
На даний момент ми записуємо весь наш вивід до термінала за допомогою
макроса println!. У більшості терміналів є два види виводу: стандартний
вивід (stdout) для загальної інформації та стандартний потік помилок
(stderr) для повідомлень про помилки. Це розрізнення дає змогу користувачам
обирати: перенаправити успішний вивід програми до файла, але все ще виводити
повідомлення про помилки на екран.
Макрос println! здатний друкувати лише до стандартного виводу, тож нам
потрібно використати щось інше, щоб друкувати до стандартного потоку помилок.
Перевірка того, куди записуються помилки
Спочатку давайте подивимося, як вміст, що друкується minigrep, наразі
записується до стандартного виводу, включно з будь-якими повідомленнями про
помилки, які ми хочемо записувати до стандартного потоку помилок замість цього.
Ми зробимо це, перенаправивши потік стандартного виводу до файла, навмисно
спричинивши помилку. Ми не будемо перенаправляти потік стандартного потоку
помилок, тож будь-який вміст, надісланий до стандартного потоку помилок,
продовжить відображатися на екрані.
Від командних рядків програм очікується, що вони надсилатимуть повідомлення про помилки до потоку стандартного потоку помилок, щоб ми все ще могли бачити повідомлення про помилки на екрані навіть якщо ми перенаправимо потік стандартного виводу до файла. Наша програма наразі поводиться не надто добре: зараз ми побачимо, що вона зберігає вивід повідомлення про помилку до файла замість цього!
Щоб продемонструвати таку поведінку, ми запустимо програму з > та шляхом до
файла, output.txt, до якого ми хочемо перенаправити потік стандартного
виводу. Ми не передаватимемо жодних аргументів, що має спричинити помилку:
$ cargo run > output.txt
Синтаксис > підказує оболонці записати вміст стандартного виводу до
output.txt замість екрана. Ми не побачили повідомлення про помилку, яке
очікували побачити на екрані, тож це означає, що воно мусило потрапити до
файла. Ось що містить output.txt:
Problem parsing arguments: not enough arguments
Ага, наше повідомлення про помилку друкується до стандартного виводу. Такі повідомлення про помилки значно корисніше друкувати до стандартного потоку помилок, щоб у файл потрапляли лише дані з успішного запуску. Ми це змінимо.
Друкування помилок до стандартного потоку помилок
Ми використаємо код у Listing 12-24, щоб змінити спосіб друкування повідомлень
про помилки. Через рефакторинг, який ми зробили раніше в цьому розділі, весь код,
що друкує повідомлення про помилки, міститься в одній функції, main.
Стандартна бібліотека надає макрос eprintln!, який друкує до стандартного
потоку помилок, тож давайте змінимо два місця, де ми викликали println! для
друку помилок, на використання 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(())
}
Тепер давайте знову запустимо програму тим самим способом, без жодних аргументів
і з перенаправленням стандартного виводу за допомогою >:
$ cargo run > output.txt
Problem parsing arguments: not enough arguments
Тепер ми бачимо помилку на екрані, а output.txt не містить нічого, що є поведінкою, яку ми очікуємо від командних рядків програм.
Давайте знову запустимо програму з аргументами, які не спричиняють помилку, але все ще перенаправимо стандартний вивід до файла, ось так:
$ cargo run -- to poem.txt > output.txt
Ми не побачимо жодного виводу в терміналі, а output.txt міститиме наші результати:
Filename: output.txt
Are you nobody, too?
How dreary to be somebody!
Це демонструє, що тепер ми використовуємо стандартний вивід для успішного виводу та стандартний потік помилок для виводу помилок, як і належить.
Підсумок
У цьому розділі ми підсумували деякі з основних концептів, які ви вже вивчили, і
розглянули, як виконувати поширені операції I/O у Rust. Використовуючи
аргументи командного рядка, файли, змінні середовища та макрос eprintln! для
друку помилок, ви тепер готові писати програми командного рядка. У поєднанні
з концептами з попередніх розділів ваш код буде добре організованим, ефективно
зберігатиме дані у відповідних структурах даних, гарно оброблятиме помилки та
буде добре протестованим.
Далі ми розглянемо деякі можливості Rust, на які вплинули функціональні мови: замикання та ітератори.