Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

การรับอาร์กิวเมนต์จากบรรทัดคำสั่ง (Accepting Command Line Arguments)

มาเริ่มสร้างโปรเจกต์ใหม่ด้วยคำสั่ง cargo new กันเหมือนเช่นเคย เราจะตั้งชื่อโปรเจกต์ของเราว่า minigrep เพื่อแยกความแตกต่างจากเครื่องมือ grep ที่คุณอาจมีอยู่แล้วในระบบของคุณ:

$ cargo new minigrep
     Created binary (application) `minigrep` project
$ cd minigrep

งานแรกคือทำให้ minigrep ยอมรับอาร์กิวเมนต์บรรทัดคำสั่งสองตัว ได้แก่ พาธของไฟล์ และสตริงที่จะค้นหา นั่นคือ เราต้องการรันโปรแกรมด้วย cargo run ตามด้วยเครื่องหมายยัติภังค์สองตัว (--) เพื่อระบุว่าอาร์กิวเมนต์ถัดไปเป็นของโปรแกรมเรา ไม่ใช่ของ cargo ตามด้วยสตริงที่ต้องการค้นหา และพาธไปยังไฟล์ที่จะค้นหา เช่นนี้:

$ cargo run -- searchstring example-filename.txt

ในตอนนี้ โปรแกรมที่สร้างโดย cargo new ยังไม่สามารถประมวลผลอาร์กิวเมนต์ที่เราส่งให้ได้ ไลบรารีที่มีอยู่ใน crates.io บางตัวสามารถช่วยเขียนโปรแกรมที่รับอาร์กิวเมนต์บรรทัดคำสั่งได้ แต่เนื่องจากคุณเพิ่งเริ่มเรียนรู้แนวคิดนี้ เรามาเขียนความสามารถนี้ด้วยตัวเองกันดีกว่า

การอ่านค่าของอาร์กิวเมนต์ (Reading the Argument Values)

เพื่อให้ minigrep สามารถอ่านค่าของอาร์กิวเมนต์บรรทัดคำสั่งที่เราส่งให้ได้ เราจะต้องใช้ฟังก์ชัน std::env::args ที่มีอยู่ในไลบรารีมาตรฐานของ Rust ฟังก์ชันนี้จะคืนค่าเป็น iterator ของอาร์กิวเมนต์บรรทัดคำสั่งที่ส่งไปยัง minigrep เราจะครอบคลุมเรื่อง iterator อย่างเต็มรูปแบบใน บทที่ 13 สำหรับตอนนี้ คุณต้องรู้เพียงรายละเอียดสองอย่างเกี่ยวกับ iterator: Iterator จะสร้างลำดับของค่า และเราสามารถเรียกใช้เมธอด collect บน iterator เพื่อเปลี่ยนให้เป็น collection เช่น เวกเตอร์ ซึ่งบรรจุสมาชิกทั้งหมดที่ iterator สร้างขึ้น

โค้ดใน Listing 12-1 ช่วยให้โปรแกรม minigrep ของคุณสามารถอ่านอาร์กิวเมนต์บรรทัดคำสั่งใดๆ ที่ส่งเข้ามา แล้วรวบรวมค่านั้นลงในเวกเตอร์

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    dbg!(args);
}

ขั้นแรก เรานำโมดูล std::env เข้ามาสู่ขอบเขตด้วยคำสั่ง use เพื่อให้สามารถใช้ฟังก์ชัน args ได้ สังเกตว่าฟังก์ชัน std::env::args นั้นซ้อนอยู่ในโมดูลสองระดับ ดังที่เราได้หารือกันใน บทที่ 7 ในกรณีที่ฟังก์ชันที่ต้องการซ้อนอยู่ในโมดูลมากกว่าหนึ่งระดับ เราเลือกที่จะนำโมดูลแม่เข้ามาสู่ขอบเขตแทนที่จะเป็นตัวฟังก์ชันโดยตรง การทำเช่นนี้ทำให้เราสามารถใช้ฟังก์ชันอื่นจาก std::env ได้ง่ายขึ้น และยังลดความคลุมเครือมากกว่าการเขียน use std::env::args แล้วเรียกใช้ฟังก์ชันด้วย args เพียงอย่างเดียว เพราะ args อาจถูกเข้าใจผิดว่าเป็นฟังก์ชันที่นิยามไว้ในโมดูลปัจจุบันได้ง่าย

ฟังก์ชัน args และ Unicode ที่ไม่ถูกต้อง

พึงสังเกตว่า std::env::args จะ เกิด panic หากอาร์กิวเมนต์ใดๆ บรรจุ Unicode ที่ไม่ถูกต้อง หากโปรแกรมของคุณจำเป็นต้องรับอาร์กิวเมนต์ที่มี Unicode ที่ไม่ถูกต้อง ให้ใช้ std::env::args_os แทน ฟังก์ชันนั้นจะคืนค่าเป็น iterator ที่สร้างค่า OsString แทนค่า String เราเลือกใช้ std::env::args ในที่นี้เพื่อความเรียบง่าย เนื่องจากค่า OsString จะแตกต่างกันไปตามแต่ละแพลตฟอร์มและทำงานด้วยซับซ้อนกว่าค่า String

ในบรรทัดแรกของ main เราเรียกใช้ env::args และใช้ collect ทันทีเพื่อเปลี่ยน iterator ให้เป็นเวกเตอร์ที่บรรจุค่าทั้งหมดที่ถูกสร้างโดย iterator เราสามารถใช้ฟังก์ชัน collect เพื่อสร้าง collection ได้หลายประเภท ดังนั้นเราจึงต้องระบุประเภทข้อมูลของ args อย่างชัดเจนว่าต้องการเวกเตอร์ของสตริง แม้ว่าคุณจะแทบไม่ต้องระบุประเภทข้อมูลใน Rust แต่ collect เป็นฟังก์ชันหนึ่งที่คุณมักจะต้องระบุประเภทข้อมูล เนื่องจาก Rust ไม่สามารถคาดเดาประเภทของ collection ที่คุณต้องการได้

สุดท้าย เราจะพิมพ์เวกเตอร์โดยใช้มาโครดีบัก ลองรันโค้ดครั้งแรกโดยไม่ใส่อาร์กิวเมนต์ และครั้งที่สองโดยใส่อาร์กิวเมนต์สองตัว:

$ 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",
]

สังเกตว่าค่าแรกในเวกเตอร์คือ "target/debug/minigrep" ซึ่งเป็นชื่อของไฟล์ไบนารีของเรา สิ่งนี้สอดคล้องกับพฤติกรรมของรายการอาร์กิวเมนต์ในภาษา C ทำให้โปรแกรมสามารถใช้ชื่อที่ถูกเรียกใช้ในการประมวลผลได้ มักจะสะดวกที่มีการเข้าถึงชื่อโปรแกรมเผื่อกรณีที่คุณต้องการพิมพ์ในข้อความแจ้งเตือน หรือเปลี่ยนพฤติกรรมของโปรแกรมตามนามแฝง (alias) บรรทัดคำสั่งที่ใช้เรียกโปรแกรม แต่สำหรับวัตถุประสงค์ของบทนี้ เราจะข้ามมันไปและบันทึกเฉพาะอาร์กิวเมนต์สองตัวที่เราต้องการเท่านั้น

การบันทึกค่าอาร์กิวเมนต์ไว้ในตัวแปร (Saving the Argument Values in Variables)

ตอนนี้โปรแกรมสามารถเข้าถึงค่าที่ระบุเป็นอาร์กิวเมนต์บรรทัดคำสั่งได้แล้ว คราวนี้เราจำเป็นต้องบันทึกค่าของอาร์กิวเมนต์ทั้งสองตัวไว้ในตัวแปร เพื่อให้เราสามารถใช้ค่านั้นได้ตลอดส่วนที่เหลือของโปรแกรม เราทำเช่นนั้นใน 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}");
}

ดังที่เราเห็นเมื่อตอนพิมพ์เวกเตอร์ ชื่อของโปรแกรมจะจับจองค่าแรกในเวกเตอร์ที่ args[0] ดังนั้นเราจึงเริ่มใส่อาร์กิวเมนต์ที่ดัชนี 1 อาร์กิวเมนต์แรกที่ minigrep รับมาคือสตริงที่เรากำลังค้นหา ดังนั้นเราจึงวาง reference ของอาร์กิวเมนต์แรกไว้ในตัวแปร query ส่วนอาร์กิวเมนต์ที่สองจะเป็นพาธไฟล์ ดังนั้นเราจึงวาง reference ของอาร์กิวเมนต์ที่สองไว้ในตัวแปร file_path

เราพิมพ์ค่าของตัวแปรเหล่านี้ชั่วคราวเพื่อพิสูจน์ว่าโค้ดทำงานตามที่เราตั้งใจไว้ ลองรันโปรแกรมนี้อีกครั้งพร้อมกับอาร์กิวเมนต์ 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

เยี่ยมเลย โปรแกรมทำงานได้แล้ว! ค่าของอาร์กิวเมนต์ที่เราต้องการกำลังถูกบันทึกลงในตัวแปรที่ถูกต้อง ในภายหลังเราจะเพิ่มการจัดการข้อผิดพลาดเพื่อรับมือกับสถานการณ์ที่อาจเกิดข้อผิดพลาด เช่น เมื่อผู้ใช้ไม่ได้ระบุอาร์กิวเมนต์ สำหรับตอนนี้ เราจะมองข้ามสถานการณ์นั้นไปก่อนและไปลุยเรื่องการเพิ่มความสามารถในการอ่านไฟล์แทน