iT邦幫忙

2024 iThome 鐵人賽

DAY 17
0

命令列介面(Command-Line Interface, CLI)是程式設計中一個常見的介面類型,尤其是在工具或系統開發中,CLI 提供了高效的用戶輸入和互動方式。Rust 的工具箱讓建立 CLI 應用變得非常簡單。在這篇文章中,我們將帶你一步步建立你的第一個 Rust CLI 應用程式,並介紹如何使用 Rust 的 std::envclap 等庫來處理命令列參數。


一、什麼是 CLI 應用程式?

CLI 應用程式允許使用者透過命令列的方式與應用程式互動。這類應用通常由一系列的指令、選項或參數組成,當使用者輸入指令並按下回車後,應用程式會根據參數進行相應的操作。

例如,一個簡單的 CLI 應用可能接受一個檔案路徑作為輸入,然後顯示該檔案的內容,或者根據提供的選項進行計算。


二、建立一個簡單的 Rust CLI 應用

1. 設定專案

首先,我們需要設定一個新的 Rust 專案。使用 cargo 工具來初始化專案。

cargo new first_cli_app
cd first_cli_app

這將建立一個名為 first_cli_app 的專案目錄,其中包含了 Cargo.toml 和一個簡單的 src/main.rs 檔案。

2. 處理命令列參數

在 CLI 應用中,處理使用者的輸入是最基本的需求。Rust 提供了 std::env 庫來處理命令列參數,我們可以使用 env::args 來獲取使用者的輸入參數。

// 引入標準庫中的 `env` 模組,用來處理環境變數及命令列參數
use std::env;

fn main() {
    // 使用 `env::args()` 函數來取得命令列參數,並將其轉換成字串向量
    let args: Vec<String> = env::args().collect();
    
    // 列印出收集到的命令列參數
    println!("收到的參數: {:?}", args);
}

當你編譯並運行這個程式時,CLI 會將使用者輸入的每個參數收集到一個向量中並列印出來。
這是一個簡單的範例,說明如何設定命令列應用,並處理命令列參數。以下是你要求的完整程式碼,包含繁體中文註解及範例使用方式:

指令範例

編譯並運行這個程式後,你可以使用以下指令來測試:

cargo run -- arg1 arg2

這將產生以下輸出(視你傳遞的參數而定):

收到的參數: ["target/debug/first_cli_app", "arg1", "arg2"]

說明

  • 第一個參數總是程式的路徑名稱(例如 target/debug/first_cli_app)。
  • 後續的參數則是使用者在命令列中輸入的其他參數(在這個例子中是 arg1, arg2)。

這樣的程式可以作為基礎,後續可以加入更多的邏輯來處理具體的參數行為,例如解析旗標或執行不同的命令。

在 Rust 中,當你使用 cargo run 來執行你的應用程式時,Rust 會自動幫你編譯程式並在專案的 target/debug/ 目錄下生成一個可執行檔案。cargo 是 Rust 的包管理工具,它提供了簡便的專案編譯和執行方式。

因此,當你使用 cargo run 時,第一個參數通常會是執行程式的完整路徑,這就是為什麼你會看到 "target/debug/first_cli_app"。它指向的是編譯後的可執行檔案的路徑,這個檔案在 target/debug/ 資料夾中,因為你使用的是 cargo run 預設的 debug 模式來編譯專案。

具體來說:

  1. target/debug/ 是 Rust 預設用來存放 Debug 模式下編譯產物的目錄。
  2. first_cli_app 是編譯後的可執行檔案名稱,這個名稱跟你專案的名稱相同。

3. 使用參數進行簡單操作

接下來,我們可以對命令列參數進行簡單的邏輯操作,例如計算兩個數字的和。假設我們希望使用者輸入兩個數字,我們可以擷取這些參數並進行加法運算。

// 引入標準庫中的 `env` 模組,用來處理環境變數及命令列參數
use std::env;

fn main() {
    // 使用 `env::args()` 函數來取得命令列參數,並將其轉換成字串向量
    let args: Vec<String> = env::args().collect();

    // 檢查參數數量是否等於 3,第一個參數為執行檔案路徑,其餘為使用者輸入的數字
    if args.len() != 3 {
        // 如果參數數量不對,提示使用格式
        println!("用法: cargo run <數字1> <數字2>");
        return;
    }

    // 將第二個和第三個參數轉換為整數
    let num1: i32 = args[1].trim().parse().expect("請輸入有效的數字");
    let num2: i32 = args[2].trim().parse().expect("請輸入有效的數字");

    // 計算兩個數字的和
    let sum = num1 + num2;
    
    // 列印出計算結果
    println!("{} 和 {} 的總和是 {}", num1, num2, sum);
}

在這個範例中,我們首先檢查參數的數量是否為 3,然後將第二和第三個參數轉換為整數,最後輸出這兩個數字的和。如果使用者輸入不正確,程式會提示輸入格式錯誤。

執行指令範例

你可以使用以下指令來執行這個應用程式:

cargo run 5 10

輸出結果範例

5 和 10 的總和是 15

程式說明

  • args.len() != 3: 確保有三個參數,第一個是執行檔的路徑,另外兩個是使用者輸入的數字。
  • args[1]args[2]: 分別取得第一個和第二個輸入數字,並轉換為整數。
  • 程式會提示輸入錯誤訊息,若提供的參數不符合要求或無法轉換為數字。

三、使用 clap 庫來增強 CLI 應用

雖然 std::env 可以用來處理簡單的命令列參數,但如果你需要處理更複雜的選項和旗標,clap 庫是更好的選擇。clap 是 Rust 中非常受歡迎的命令列解析庫,它能自動生成幫助訊息,並提供靈活的參數和旗標處理功能。

1. 安裝 clap

Cargo.toml 檔案中加入 clap 庫依賴:

[dependencies]
clap = { version = "4.0", features = ["derive"] }

然後,在 main.rs 中引入 clap 並定義命令列參數。

2. 使用 clap 定義參數

這是一個使用 clap 庫來定義命令列參數的範例,讓參數解析更簡單且具有更好的可讀性。以下是完整程式碼,包含繁體中文註解與詳細說明:

// 引入 `clap` 庫中的 `Parser`,用來自動生成命令列解析邏輯
use clap::Parser;

/// 這是一個簡單的 CLI 應用程式,用來計算兩個數字的總和
/// `#[derive(Parser)]` 巨集會自動為結構體 `Cli` 生成命令列解析功能
#[derive(Parser)]
struct Cli {
    /// 第一個數字,透過命令列輸入
    num1: i32,
    
    /// 第二個數字,透過命令列輸入
    num2: i32,
}

fn main() {
    // 使用 `Cli::parse()` 來解析命令列輸入的參數並將其傳入 `args` 結構體
    let args = Cli::parse();

    // 計算兩個數字的總和
    let sum = args.num1 + args.num2;
    
    // 列印出計算結果,告知使用者兩個數字的和
    println!("{} 和 {} 的總和是 {}", args.num1, args.num2, sum);
}
  1. clap::Parser:

    • clap 是一個非常強大的命令列參數解析庫,能夠處理多種複雜的命令列參數需求。
    • 我們使用 derive 巨集中的 Parser 來自動生成命令列參數解析邏輯,無需手動寫入大量的參數解析程式碼,讓應用程式更簡潔。
  2. #[derive(Parser)]:

    • 這個屬性標記會告訴 Rust 編譯器,該結構體將會具備 clap 提供的命令列解析功能。
    • 在這裡,我們定義了一個 Cli 結構體,包含兩個屬性 num1num2,分別用來接收使用者在命令列中輸入的兩個數字。
  3. 命令列參數的解析:

    • Cli::parse() 會自動解析命令列參數並將它們轉換為 Cli 結構體的成員。這使得程式不需要手動處理參數數量或轉換,clap 會自動幫我們完成這些工作。
  4. 計算與顯示結果:

    • 程式將 args.num1args.num2 兩個屬性相加,然後使用 println! 列印出兩個數字的總和。

使用方式

編譯並運行這個程式後,你可以在命令列中輸入以下指令來執行:

cargo run -- 5 10

輸出結果將會是:

5 和 10 的總和是 15

為什麼使用 clap

使用 clap 庫來處理命令列參數的優點包括:

  • 更簡潔的程式碼:不需要手動檢查參數數量或轉換類型,clap 自動完成這些工作。
  • 更強大的功能clap 支援多種參數格式、旗標及其他命令列特性,能輕鬆處理更複雜的需求。

這使得 CLI 應用程式的開發變得更加方便且具擴展性。如果你想要擴充應用程式的命令列功能,clap 會提供豐富的工具來幫助你實現。

至於巨集(Macro)的話,就是另一個故事了,我們之後再來討論

3. 使用 clap 增加選項和旗標

以下是使用 clap 增加選項和旗標的範例程式碼,包含繁體中文註解及中文的 println! 輸出內容:

// 引入 `clap` 庫中的 `Parser`,用來自動生成命令列解析邏輯
use clap::Parser;

/// 使用 `#[derive(Parser)]` 來定義命令列參數與旗標
/// `#[command(name = "adder")]` 定義 CLI 的名稱
/// `#[command(about = "A simple CLI for adding two numbers")]` 定義簡介
#[derive(Parser)]
#[command(name = "adder")]
#[command(about = "一個簡單的 CLI 程式,用來加總兩個數字")]
struct Cli {
    /// 第一個數字,由命令列輸入
    num1: i32,
    
    /// 第二個數字,由命令列輸入
    num2: i32,
    
    /// 開啟詳細模式(verbose mode),短選項為 `-v`,長選項為 `--verbose`
    #[arg(short, long)]
    verbose: bool,
}

fn main() {
    // 使用 `Cli::parse()` 來解析命令列輸入的參數
    let args = Cli::parse();

    // 計算兩個數字的總和
    let sum = args.num1 + args.num2;

    // 如果使用者有開啟 `--verbose` 或 `-v`,則顯示詳細訊息
    if args.verbose {
        println!("正在將 {} 和 {} 進行加總", args.num1, args.num2);
    }

    // 列印出加總的結果
    println!("結果: {}", sum);
}
  1. #[command(name = "adder")]#[command(about = "...")]

    • 這些是 clap 提供的巨集,用來為你的 CLI 應用設定名稱和簡介。在執行 --help 時,會顯示這些內容。
  2. 旗標 verbose

    • #[arg(short, long)] 定義了一個選項或旗標,可以通過短選項 -v 或長選項 --verbose 啟用。
    • 當啟用了 --verbose 旗標時,程式會輸出額外的詳細訊息,例如顯示正在加總的兩個數字。
    • #[arg(short, long)] 會自動將長選項的字首當作短選項,但是如果有兩個相同的長選項,短選項也會被自動設定相同,因此假設有兩組長選項為verbose, version,則輸入 -v 就會顯示 error: argument short option '-v' is already in use
    • 如果需要手動設定短選項,則能夠使用 #[arg(short = 'v', verbose)], #[arg(short = 'V', version)] ,這種方式來定義,請記住短選項只能設定一個字母,當參數有需要使用多個字母表達,應使用長選項而放棄使用短選項。
  3. 運作邏輯

    • 使用 Cli::parse() 來解析命令列參數,如果 args.verbosetrue,就會列印出詳細的加總過程。

使用方式

可以使用以下指令來執行這個程式:

cargo run -- 5 10 --verbose

輸出結果範例

正在將 5 和 10 進行加總
結果: 15

如果沒有加上 --verbose

結果: 15

當使用 clap 定義的 CLI 應用程式執行 --help 時,clap 會自動生成一個幫助訊息,告訴使用者如何使用這個應用程式。以下是根據我們的範例程式碼,執行 cargo run -- --help 時所會看到的結果範例:

$ cargo run -- --help
adder 
一個簡單的 CLI 程式,用來加總兩個數字

Usage: adder <NUM1> <NUM2> [OPTIONS]

Arguments:
  <NUM1>  第一個數字,由命令列輸入
  <NUM2>  第二個數字,由命令列輸入

Options:
  -v, --verbose  開啟詳細模式(verbose mode)
  -h, --help     列印出幫助訊息
  1. Usage:

    • Usage: adder <NUM1> <NUM2> [OPTIONS] 提示如何使用這個 CLI 應用,NUM1NUM2 是兩個必需的參數,而 [OPTIONS] 表示可以選擇性地加上其他選項,例如 --verbose
  2. Arguments:

    • NUM1NUM2 是應用程式需要的兩個參數,分別代表兩個要加總的數字。
  3. Options:

    • -v, --verbose 是一個可選的旗標,當啟用時,會顯示詳細模式。
    • -h, --helpclap 自動生成的幫助訊息旗標,用來列印幫助訊息,讓使用者知道如何使用這個應用程式。

這個幫助訊息的內容會隨著你在 #[command(about = "...")]#[arg(...)] 裡面的設定而自動調整,使 CLI 應用更加方便使用和理解。

這樣的設計讓使用者可以選擇是否需要詳細的輸出,增加了應用程式的靈活性clap 的選項和旗標功能非常有用,可用來輕鬆擴展以支援更多命令列選項。


四、CLI 錯誤處理

以下是將程式碼內的英文改成繁體中文並加入繁體中文註解和詳細說明的版本:

// 引入 `clap` 庫中的 `Parser`,用來自動解析命令列參數
use clap::Parser;

/// 使用 `#[derive(Parser)]` 來定義命令列參數
#[derive(Parser)]
struct Cli {
    /// 第一個數字,由命令列輸入
    num1: i32,

    /// 第二個數字,由命令列輸入
    num2: i32,
}

fn main() {
    // 使用 `Cli::parse()` 來解析命令列輸入的參數
    let args = Cli::parse();

    // 檢查使用者輸入的數字是否有效(不能是負數)
    if args.num1 < 0 || args.num2 < 0 {
        // 使用 `eprintln!` 列印出錯誤訊息,並以標準錯誤輸出
        eprintln!("錯誤: 請輸入非負的數字。");
        // 結束程式並回傳錯誤碼 1,表示有錯誤發生
        std::process::exit(1);
    }

    // 計算兩個數字的總和
    let sum = args.num1 + args.num2;

    // 列印結果,告知使用者加總的結果
    println!("{} 和 {} 的總和是 {}", args.num1, args.num2, sum);
}
  1. 錯誤訊息處理

    • 當使用者輸入了負數時,程式會使用 eprintln! 來列印錯誤訊息,eprintln! 是 Rust 中用來向標準錯誤輸出(stderr)列印訊息的函式。
    • 使用 std::process::exit(1) 可以讓程式以錯誤代碼 1 結束執行,這是一個通用的方式來表示程式發生錯誤。
  2. 負數檢查

    • 程式首先檢查輸入的兩個數字是否為負數,如果有任一數字為負,則觸發錯誤訊息並終止程式。這樣的檢查確保應用程式僅處理有效的正整數。
  3. 成功處理的情況

    • 如果輸入的數字都合法(非負數),則會計算這兩個數字的總和並將結果列印出來。

使用方式

你可以使用以下指令來執行這個程式:

cargo run -- -- -5 10

輸出結果範例(輸入錯誤時)

錯誤: 請輸入非負的數字。

輸出結果範例(輸入正確時)

5 和 10 的總和是 15

這個範例展示了基本的錯誤處理方式,當命令列參數不符合預期時,我們可以友好地提示使用者,並以適當的方式結束程式。這種處理方式在實際的 CLI 應用程式中非常常見,可以有效提升使用者體驗。


解釋CLI當中的 --

在 Rust 的 cargo run 命令中,-- 的作用是將指令中的參數明確區分為兩部分:一部分屬於 cargo 命令的參數,另一部分屬於執行的程式的參數。

具體來說:

cargo run -- -- -5 10 指令中

  1. 第一個 --:告訴 cargo 命令,接下來的所有參數都不再是 cargo 本身的參數,而是應用程式的參數。cargo 在處理命令時會解析它自己的參數(例如 cargo runcargo build 等),當你加上 --,它就會停止解析並將接下來的所有內容傳遞給你所執行的應用程式。

  2. 第二個 --:這是告訴你的應用程式,它所收到的接下來的參數是純粹的值,而不是命令列選項。這通常用來處理帶有負號的數值,因為許多 CLI 工具會將帶有 - 的參數視為選項(例如 -v 表示 verbose 模式),而不是數字。因此,當你希望傳遞 -5 這樣的負數給應用程式,必須在參數前加上 --,讓程式知道 -5 是一個值,而不是一個選項。

如果省略第二個 --

當你直接執行 cargo run -- -5 10 時,Rust 的命令列解析庫(例如 clap)會將 -5 誤認為是一個選項,而不是數字。因此會出現「error: unexpected argument '-5' found」的錯誤。

五、總結

在這篇文章中,我們透過實作一個簡單的 CLI 應用程式,介紹了 Rust 如何處理命令列參數並建立功能豐富的命令列工具。從最基本的命令列解析,到使用 clap 庫來增強應用程式的功能,Rust 提供了非常強大且靈活的工具來構建 CLI 應用程式。

  • 我們學習了如何使用 std::env::args 來獲取命令列參數。
  • 使用 clap 庫來建立更複雜的 CLI 應用。
  • 處理選項、旗標和錯誤訊息,確保應用程式在各種情況下都能運作順暢。

上一篇
[Day 16] 泛型與特徵物件:提升 Rust 代碼的彈性與重用性
下一篇
[Day 18] Rust 與 WebAssembly:在網頁上使用 Rust
系列文
從 Python 開發者的角度學習 Rust —— 從語法基礎到實戰應用30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言