표준 출력 대신 표준 오류로 출력하기
표준 출력 대신 표준 오류로 출력하기
지금은 모든 출력을 println! 매크로로 터미널에 쓰고 있어요. 대부분의 터미널에는 두 종류의 출력이 있습니다. 일반 정보를 위한 표준 출력(stdout)과 오류 메시지를 위한 표준 오류(stderr)죠. 이 구분 덕분에 사용자는 프로그램의 성공적인 출력은 파일로 보내면서도, 오류 메시지는 화면에 계속 출력되게 할 수 있어요.
println! 매크로는 표준 출력에만 출력할 수 있으므로, 표준 오류로 출력하려면 다른 것을 써야 합니다.
출처: The Rust Book
오류가 어디에 쓰이는지 확인하기
먼저 minigrep이 출력하는 내용이 표준 오류로 보내고 싶은 오류 메시지를 포함해서 현재 어떻게 표준 출력으로 쓰이고 있는지 관찰해 볼게요. 표준 출력 스트림을 파일로 리다이렉트하면서 의도적으로 오류를 일으키는 방식으로 확인할 거예요. 표준 오류 스트림은 리다이렉트하지 않으므로, 표준 오류로 보내진 내용은 계속 화면에 표시될 거예요.
명령줄 프로그램은 오류 메시지를 표준 오류 스트림으로 보내서, 표준 출력 스트림을 파일로 리다이렉트해도 오류 메시지를 화면에서 계속 볼 수 있게 하는 것이 기대하는 동작입니다. 우리 프로그램은 지금 예의 바르지 않은데요, 이제 곧 오류 메시지 출력을 파일로 저장해 버리는 걸 보게 될 거예요.
이 동작을 시연하려면 >와, 표준 출력 스트림을 리다이렉트할 파일 경로인 output.txt를 써서 프로그램을 실행할게요. 인자는 아무것도 넘기지 않아서 오류가 나야 합니다:
$ cargo run > output.txt
> 문법은 셸에게 표준 출력의 내용을 화면 대신 output.txt에 쓰라고 말해요. 기대했던 오류 메시지가 화면에 출력되는 걸 보지 못했으니, 그 오류 메시지는 결국 파일 안에 들어갔다는 뜻이에요. output.txt의 내용은 이렇습니다:
Problem parsing arguments: not enough arguments
네, 우리 오류 메시지가 표준 출력으로 출력되고 있어요. 이런 오류 메시지를 표준 오류로 출력하고, 그래서 성공적인 실행에서 온 데이터만 파일에 들어가게 하는 것이 훨씬 유용해요. 그렇게 바꿔 볼게요.
표준 오류로 오류 출력하기
Listing 12-24의 코드로 오류 메시지가 출력되는 방식을 바꿀게요. 이 챕터 앞부분에서 리팩터링했기 때문에 오류 메시지를 출력하는 코드는 모두 main 함수 한 곳에 있어요. 표준 라이브러리에서 표준 오류 스트림으로 출력하는 eprintln! 매크로를 제공하니, 오류를 출력하려고 println!을 호출하던 두 곳을 eprintln!으로 바꿉니다.
파일명: src/main.rs
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(())
}
Listing 12-24: 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에서 흔한 I/O 작업을 수행하는 방법을 다뤘어요. 명령줄 인자, 파일, 환경 변수, 그리고 오류 출력용 eprintln! 매크로를 사용하면서 이제 명령줄 애플리케이션을 작성할 준비가 됐습니다. 이전 챕터의 개념들과 합치면, 여러분의 코드는 잘 조직되어 있고, 적절한 데이터 구조에 데이터를 효과적으로 저장하며, 오류를 잘 처리하고, 잘 테스트된 코드가 될 거예요.
다음으로는 함수형 언어에서 영향을 받은 Rust 기능인 클로저와 반복자를 살펴볼게요.