รับ Command Line Argument
มาสร้างโปรเจกต์ใหม่ด้วย cargo new เหมือนเคย เราจะตั้งชื่อโปรเจกต์ของ
เราว่า minigrep เพื่อแยกจากเครื่องมือ grep ที่คุณอาจมีในระบบแล้ว:
$ cargo new minigrep
Created binary (application) `minigrep` project
$ cd minigrep
งานแรกคือทำให้ minigrep รับสองอาร์กิวเมนต์ command line — file path
และ string ที่จะค้นหา นั่นคือ เราต้องการรันโปรแกรมของเราด้วย cargo run,
hyphen สองตัวเพื่อระบุว่าอาร์กิวเมนต์ต่อไปนี้สำหรับโปรแกรมของเราไม่ใช่
สำหรับ cargo, string ที่จะค้นหา และ path ของไฟล์ที่จะค้นหาใน แบบนี้:
$ cargo run -- searchstring example-filename.txt
ตอนนี้ โปรแกรมที่ cargo new สร้างประมวลผลอาร์กิวเมนต์ที่เราให้มันไม่
ได้ บาง library ที่มีอยู่บน crates.io ช่วยใน
การเขียนโปรแกรมที่รับอาร์กิวเมนต์ command line ได้ แต่เพราะคุณกำลัง
เรียนแนวคิดนี้ มา implement ความสามารถนี้ด้วยตัวเองกัน
อ่านค่า Argument
เพื่อให้ minigrep อ่านค่าของอาร์กิวเมนต์ command line ที่เราส่งให้ได้
เราจะต้องการฟังก์ชัน std::env::args ที่ standard library ของ Rust
ให้มา ฟังก์ชันนี้ return iterator ของอาร์กิวเมนต์ command line ที่
ส่งให้ minigrep เราจะครอบคลุม iterator ทั้งหมดใน
บทที่ 13 ตอนนี้ คุณต้องรู้แค่สองรายละเอียด
เกี่ยวกับ iterator — iterator สร้างชุดของค่า และเราเรียกเมธอด collect
บน iterator เพื่อเปลี่ยนให้เป็น collection เช่น vector ซึ่งบรรจุ
element ทั้งหมดที่ iterator สร้างได้
โค้ดใน Listing 12-1 ทำให้โปรแกรม minigrep ของคุณอ่านอาร์กิวเมนต์
command line ใด ๆ ที่ส่งให้ และ collect ค่าเข้า vector
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
dbg!(args);
}
ก่อนอื่น เรานำโมดูล std::env เข้า scope ด้วย statement use เพื่อให้
เราใช้ฟังก์ชัน args ของมันได้ สังเกตว่าฟังก์ชัน std::env::args ถูก
ซ้อนในโมดูลสองระดับ ดังที่เราพูดถึงใน
บทที่ 7 ในกรณีที่ฟังก์ชันที่ต้องการ
ถูกซ้อนในมากกว่าหนึ่งโมดูล เราเลือกนำโมดูล parent เข้า scope แทน
ฟังก์ชัน เมื่อทำเช่นนั้น เราใช้ฟังก์ชันอื่นจาก std::env ได้ง่าย ๆ
มันยังกำกวมน้อยกว่าการเพิ่ม use std::env::args แล้วเรียกฟังก์ชันด้วย
แค่ args เพราะ args อาจถูกเข้าใจผิดง่าย ๆ ว่าเป็นฟังก์ชันที่นิยาม
ในโมดูลปัจจุบัน
ฟังก์ชัน args และ Unicode ที่ไม่ valid
สังเกตว่า std::env::args จะ panic ถ้าอาร์กิวเมนต์ใดมี Unicode ที่
ไม่ valid ถ้าโปรแกรมของคุณต้องรับอาร์กิวเมนต์ที่มี Unicode ที่ไม่
valid ใช้ std::env::args_os แทน ฟังก์ชันนั้น return iterator ที่
สร้างค่า OsString แทนค่า String เราเลือกใช้ std::env::args
ที่นี่เพื่อความง่ายเพราะค่า OsString ต่างกันตามแต่ละ platform และ
ทำงานด้วยซับซ้อนกว่าค่า String
ในบรรทัดแรกของ main เราเรียก env::args และเราใช้ collect ทันที
เพื่อเปลี่ยน iterator เป็น vector ที่บรรจุค่าทั้งหมดที่ iterator สร้าง
เราใช้ฟังก์ชัน collect เพื่อสร้าง collection หลายประเภทได้ ดังนั้น
เราระบุ type ของ args อย่างชัดเจนเพื่อระบุว่าเราต้องการ vector ของ
string แม้คุณจะแทบไม่ต้อง annotate type ใน Rust collect เป็นฟังก์ชัน
หนึ่งที่คุณมักต้อง annotate เพราะ Rust ไม่สามารถ infer ประเภทของ
collection ที่คุณต้องการได้
สุดท้าย เรา print vector โดยใช้ debug macro มาลองรันโค้ดก่อน — ก่อน อื่นโดยไม่มีอาร์กิวเมนต์ จากนั้นด้วยสองอาร์กิวเมนต์:
$ cargo run
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.61s
Running `target/debug/minigrep`
[src/main.rs:5:5] args = [
"target/debug/minigrep",
]
$ cargo run -- needle haystack
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.57s
Running `target/debug/minigrep needle haystack`
[src/main.rs:5:5] args = [
"target/debug/minigrep",
"needle",
"haystack",
]
สังเกตว่าค่าแรกใน vector คือ "target/debug/minigrep" ซึ่งเป็นชื่อของ
binary ของเรา นี่ตรงกับพฤติกรรมของ list อาร์กิวเมนต์ใน C ที่ให้
โปรแกรมใช้ชื่อที่ใช้เรียกพวกมันใน execution ของพวกมัน มันมักสะดวกที่
จะมีสิทธิ์เข้าถึงชื่อโปรแกรมในกรณีที่คุณต้องการ print มันในข้อความ
หรือเปลี่ยนพฤติกรรมของโปรแกรมตาม alias command line ที่ใช้เรียกโปรแกรม
แต่สำหรับจุดประสงค์ของบทนี้ เราจะ ignore มันและบันทึกเฉพาะสองอาร์กิวเมนต์
ที่เราต้องการ
บันทึกค่า Argument ในตัวแปร
ตอนนี้โปรแกรมเข้าถึงค่าที่ระบุเป็นอาร์กิวเมนต์ command line ได้ ตอนนี้ เราต้องบันทึกค่าของสองอาร์กิวเมนต์ในตัวแปรเพื่อให้เราใช้ค่าตลอดส่วนที่ เหลือของโปรแกรมได้ เราทำสิ่งนั้นใน Listing 12-2
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
let query = &args[1];
let file_path = &args[2];
println!("Searching for {query}");
println!("In file {file_path}");
}
ดังที่เราเห็นเมื่อเรา print vector ชื่อของโปรแกรมใช้ค่าแรกใน vector
ที่ args[0] ดังนั้นเราเริ่มอาร์กิวเมนต์ที่ index 1 อาร์กิวเมนต์แรก
ที่ minigrep รับคือ string ที่เรากำลังค้นหา ดังนั้นเราวาง reference
ของอาร์กิวเมนต์แรกในตัวแปร query อาร์กิวเมนต์ที่สองจะเป็น file path
ดังนั้นเราวาง reference ของอาร์กิวเมนต์ที่สองในตัวแปร file_path
เรา print ค่าของตัวแปรเหล่านี้ชั่วคราวเพื่อพิสูจน์ว่าโค้ดทำงานตามที่
เราตั้งใจ มารันโปรแกรมนี้อีกครั้งด้วยอาร์กิวเมนต์ test และ
sample.txt:
$ cargo run -- test sample.txt
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
Running `target/debug/minigrep test sample.txt`
Searching for test
In file sample.txt
ดี โปรแกรมทำงาน! ค่าของอาร์กิวเมนต์ที่เราต้องการกำลังถูกบันทึกในตัวแปร ที่ถูกต้อง ภายหลังเราจะเพิ่มการจัดการ error เพื่อจัดการกับสถานการณ์ ที่อาจผิดพลาดบางอย่าง เช่นเมื่อ user ไม่ให้อาร์กิวเมนต์ ตอนนี้เราจะ ignore สถานการณ์นั้นและทำงานเพิ่มความสามารถในการอ่านไฟล์แทน