Перенаправление ошибок в стандартный поток ошибок
Сейчас мы записываем весь наш вывод в терминал с помощью макроса println!. В
большинстве терминалов есть два вида вывода: стандартный вывод (stdout) для
общей информации и стандартный поток ошибок (stderr) для сообщений об
ошибках. Это различие позволяет пользователям направлять успешный вывод
программы в файл, но при этом продолжать печатать сообщения об ошибках на
экран.
Макрос println! способен печатать только в стандартный вывод, поэтому для
печати в стандартный поток ошибок нам нужно использовать что-то другое.
Проверка того, куда записываются ошибки
Сначала посмотрим, как содержимое, печатаемое minigrep, сейчас записывается
в стандартный вывод, включая любые сообщения об ошибках, которые мы вместо
этого хотим записывать в стандартный поток ошибок. Мы сделаем это, перенаправив
поток стандартного вывода в файл и намеренно вызвав ошибку. Поток стандартных
ошибок мы перенаправлять не будем, поэтому любое содержимое, отправленное в
стандартный поток ошибок, продолжит отображаться на экране.
От программ командной строки ожидается, что они отправляют сообщения об ошибках в стандартный поток ошибок, чтобы мы все равно могли видеть сообщения об ошибках на экране, даже если перенаправим поток стандартного вывода в файл. Сейчас наша программа ведет себя неправильно: мы увидим, что она сохраняет вывод сообщения об ошибке в файл!
Чтобы продемонстрировать это поведение, мы запустим программу с > и путем к
файлу output.txt, в который хотим перенаправить поток стандартного вывода.
Мы не будем передавать аргументы, что должно вызвать ошибку:
$ cargo run > output.txt
Синтаксис > говорит оболочке записать содержимое стандартного вывода в
output.txt вместо экрана. Мы не увидели ожидаемое сообщение об ошибке на
экране, значит, оно должно было оказаться в файле. Вот что содержит
output.txt:
Problem parsing arguments: not enough arguments
Да, наше сообщение об ошибке печатается в стандартный вывод. Гораздо полезнее, чтобы такие сообщения об ошибках печатались в стандартный поток ошибок: тогда в файл попадут только данные успешного запуска. Мы это изменим.
Печать ошибок в стандартный поток ошибок
Мы используем код из листинга 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(())
}
eprintln!Теперь снова запустим программу тем же способом: без аргументов и с
перенаправлением стандартного вывода с помощью >:
$ cargo run > output.txt
Problem parsing arguments: not enough arguments
Теперь мы видим ошибку на экране, а output.txt ничего не содержит; именно такого поведения мы ожидаем от программ командной строки.
Снова запустим программу с аргументами, которые не вызывают ошибку, но все еще перенаправим стандартный вывод в файл:
$ cargo run -- to poem.txt > output.txt
Мы не увидим вывода в терминале, а output.txt будет содержать наши результаты:
Имя файла: output.txt
Are you nobody, too?
How dreary to be somebody!
Это показывает, что теперь мы используем стандартный вывод для успешного вывода и стандартный поток ошибок для вывода ошибок, как и следует.
Итоги
В этой главе мы повторили некоторые важные концепции, которые вы уже изучили,
и рассмотрели, как выполнять распространенные операции ввода-вывода в Rust.
Используя аргументы командной строки, файлы, переменные окружения и макрос
eprintln! для печати ошибок, вы теперь готовы писать приложения командной
строки. В сочетании с концепциями из предыдущих глав ваш код будет хорошо
организован, будет эффективно хранить данные в подходящих структурах данных,
аккуратно обрабатывать ошибки и будет хорошо протестирован.
Далее мы изучим некоторые возможности Rust, на которые повлияли функциональные языки: замыкания и итераторы.