การเปลี่ยนทิศทางข้อผิดพลาดไปยัง Standard Error (Redirecting Errors to Standard Error)
ในตอนนี้ เรากำลังเขียนผลลัพธ์ทั้งหมดของเราไปยังเทอร์มินัลโดยใช้มาโคร println! ในเทอร์มินัลส่วนใหญ่ มีผลลัพธ์สองประเภทคือ: ผลลัพธ์มาตรฐาน (standard output - stdout) สำหรับข้อมูลทั่วไป และ ข้อผิดพลาดมาตรฐาน (standard error - stderr) สำหรับข้อความข้อผิดพลาด ความแตกต่างนี้ช่วยให้ผู้ใช้สามารถเลือกส่งทิศทางผลลัพธ์การทำงานที่สำเร็จของโปรแกรมไปยังไฟล์ ในขณะที่ยังคงพิมพ์ข้อความข้อผิดพลาดบนหน้าจอได้
มาโคร println! มีความสามารถเพียงแค่พิมพ์ไปยัง standard output เท่านั้น ดังนั้นเราจึงต้องใช้อย่างอื่นเพื่อพิมพ์ไปยัง standard error
การตรวจสอบจุดที่ข้อผิดพลาดถูกเขียนออกไป (Checking Where Errors Are Written)
ขั้นแรก มาสังเกตวิธีที่เนื้อหาซึ่งพิมพ์โดย minigrep ถูกเขียนไปยัง standard output ในปัจจุบัน รวมถึงข้อความข้อผิดพลาดใดๆ ที่เราต้องการเขียนไปยัง standard error แทน เราจะทำเช่นนั้นโดยการเปลี่ยนทิศทางสตรีม standard output ไปยังไฟล์ ในขณะที่เจตนาทำให้เกิดข้อผิดพลาด เราจะไม่เปลี่ยนทิศทางสตรีม standard error ดังนั้นเนื้อหาใดๆ ที่ส่งไปยัง standard error จะยังคงแสดงบนหน้าจอต่อไป
โปรแกรมบรรทัดคำสั่งคาดว่าจะส่งข้อความข้อผิดพลาดไปยังสตรีม standard error เพื่อให้เรายังคงมองเห็นข้อความข้อผิดพลาดบนหน้าจอ แม้ว่าเราจะเปลี่ยนทิศทางสตรีม standard output ไปยังไฟล์ก็ตาม โปรแกรมของเราในตอนนี้ยังทำงานได้ไม่ดีนัก: เรากำลังจะเห็นว่ามันบันทึกข้อความข้อผิดพลาดลงในไฟล์แทน!
เพื่อสาธิตพฤติกรรมนี้ เราจะรันโปรแกรมด้วย > และพาธไฟล์ output.txt ที่เราต้องการเปลี่ยนทิศทางสตรีม standard output ไป เราจะไม่ส่งอาร์กิวเมนต์ใดๆ ซึ่งควรจะทำให้เกิดข้อผิดพลาด:
$ cargo run > output.txt
ไวยากรณ์ > บอกให้เชลล์เขียนเนื้อหาของ standard output ลงใน output.txt แทนที่จะเป็นหน้าจอ เราไม่เห็นข้อความข้อผิดพลาดที่เราคาดว่าจะพิมพ์บนหน้าจอ ซึ่งหมายความว่ามันต้องไปจบลงในไฟล์ นี่คือสิ่งที่ output.txt บรรจุอยู่:
Problem parsing arguments: not enough arguments
ใช่แล้ว ข้อความข้อผิดพลาดของเรากำลังถูกพิมพ์ไปยัง standard output การพิมพ์ข้อความข้อผิดพลาดประเภทนี้ไปยัง standard error จะมีประโยชน์กว่ามาก เพื่อให้เฉพาะข้อมูลจากการรันที่สำเร็จเท่านั้นไปจบลงในไฟล์ เราจะเปลี่ยนสิ่งนั้น
การพิมพ์ข้อผิดพลาดไปยัง Standard Error (Printing Errors to Standard Error)
เราจะใช้โค้ดใน Listing 12-24 เพื่อเปลี่ยนวิธีพิมพ์ข้อความข้อผิดพลาด เนื่องจากการรีแฟกเตอร์ที่เราทำในตอนต้นของบทนี้ โค้ดทั้งหมดที่พิมพ์ข้อความข้อผิดพลาดจึงอยู่ในฟังก์ชันเดียวคือ main ไลบรารีมาตรฐานมีมาโคร eprintln! ที่พิมพ์ไปยังสตรีม standard error ดังนั้นให้เราเปลี่ยนจุดสองจุดที่เราเคยเรียกใช้ 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(())
}
คราวนี้มารันโปรแกรมอีกครั้งในรูปแบบเดิม โดยไม่ใส่อาร์กิวเมนต์ใดๆ และเปลี่ยนทิศทาง standard output ด้วย >:
$ cargo run > output.txt
Problem parsing arguments: not enough arguments
ตอนนี้เราเห็นข้อผิดพลาดบนหน้าจอ และไฟล์ output.txt ไม่มีอะไรเลย ซึ่งเป็นพฤติกรรมที่เราคาดหวังจากโปรแกรมบรรทัดคำสั่ง
ลองรันโปรแกรมอีกครั้งพร้อมกับอาร์กิวเมนต์ที่ไม่ทำให้เกิดข้อผิดพลาด แต่ยังคงเปลี่ยนทิศทาง standard output ไปยังไฟล์ เช่นนี้:
$ cargo run -- to poem.txt > output.txt
เราจะไม่เห็นผลลัพธ์ใดๆ ในเทอร์มินัล และ output.txt จะบรรจุผลลัพธ์ของเรา:
Filename: output.txt
Are you nobody, too?
How dreary to be somebody!
สิ่งนี้แสดงให้เห็นว่าตอนนี้เราใช้ standard output สำหรับผลลัพธ์ที่สำเร็จ และใช้ standard error สำหรับผลลัพธ์ข้อผิดพลาดอย่างเหมาะสมแล้ว
สรุป (Summary)
บทนี้ได้สรุปคอนเซปต์หลักบางประการที่คุณได้เรียนรู้มาจนถึงตอนนี้ และครอบคลุมวิธีการดำเนินการ I/O ทั่วไปใน Rust โดยการใช้อาร์กิวเมนต์บรรทัดคำสั่ง, ไฟล์, ตัวแปรสภาพแวดล้อม และมาโคร eprintln! สำหรับการพิมพ์ข้อผิดพลาด ตอนนี้คุณก็พร้อมแล้วที่จะเขียนแอปพลิเคชันบรรทัดคำสั่ง เมื่อรวมกับคอนเซปต์ในบทก่อนหน้า โค้ดของคุณจะถูกจัดระเบียบอย่างดี จัดเก็บข้อมูลอย่างมีประสิทธิภาพในโครงสร้างข้อมูลที่เหมาะสม จัดการข้อผิดพลาดอย่างสวยงาม และได้รับการทดสอบเป็นอย่างดี
ถัดไป เราจะสำรวจฟีเจอร์ของ Rust ที่ได้รับอิทธิพลจากภาษาสไตล์ฟังก์ชันนัล (functional languages): closures และ iterators