Chấp nhận đối số dòng lệnh
Hãy tạo một dự án mới với, như thường lệ, cargo new
. Chúng ta sẽ gọi dự án của
mình là minigrep
để phân biệt nó với công cụ grep
mà có thể bạn đã có trên
hệ thống của mình.
$ cargo new minigrep
Created binary (application) `minigrep` project
$ cd minigrep
Nhiệm vụ đầu tiên là làm cho minigrep
chấp nhận hai đối số dòng lệnh của nó:
đường dẫn tệp và một chuỗi để tìm kiếm. Nghĩa là, chúng ta muốn có thể chạy
chương trình của mình với cargo run
, hai dấu gạch ngang để chỉ ra rằng các đối
số tiếp theo là dành cho chương trình của chúng ta chứ không phải cho cargo
,
một chuỗi để tìm kiếm, và một đường dẫn đến một tệp để tìm kiếm, như sau:
$ cargo run -- searchstring example-filename.txt
Hiện tại, chương trình được tạo ra bởi cargo new
không thể xử lý các đối số mà
chúng ta cung cấp. Một số thư viện hiện có trên crates.io
có thể giúp viết một chương trình chấp nhận đối số dòng lệnh, nhưng bởi vì bạn
đang học khái niệm này, hãy tự triển khai khả năng này.
Đọc giá trị đối số
Để cho phép minigrep
đọc các giá trị của đối số dòng lệnh mà chúng ta truyền
vào nó, chúng ta sẽ cần hàm std::env::args
được cung cấp trong thư viện chuẩn
của Rust. Hàm này trả về một iterator của các đối số dòng lệnh được truyền vào
minigrep
. Chúng ta sẽ thảo luận đầy đủ về iterator trong Chương
13. Hiện tại, bạn chỉ cần biết hai chi tiết về iterator: iterator tạo ra một chuỗi
giá trị, và chúng ta có thể gọi phương thức collect
trên một iterator để chuyển
nó thành một bộ sưu tập, chẳng hạn như một vector, chứa tất cả các phần tử mà iterator
tạo ra.
Mã trong Listing 12-1 cho phép chương trình minigrep
của bạn đọc bất kỳ đối số
dòng lệnh nào được truyền vào nó, và sau đó thu thập các giá trị vào một vector.
use std::env; fn main() { let args: Vec<String> = env::args().collect(); dbg!(args); }
Đầu tiên chúng ta đưa mô-đun std::env
vào phạm vi với câu lệnh use
để chúng
ta có thể sử dụng hàm args
của nó. Lưu ý rằng hàm std::env::args
được lồng
trong hai cấp độ mô-đun. Như chúng ta đã thảo luận trong Chương
7, trong trường hợp mà hàm mong muốn được
lồng trong nhiều hơn một mô-đun, chúng ta đã chọn đưa mô-đun cha vào phạm vi
thay vì hàm. Bằng cách này, chúng ta có thể dễ dàng sử dụng các hàm khác từ
std::env
. Nó cũng ít gây nhầm lẫn hơn việc thêm use std::env::args
và sau đó
gọi hàm chỉ với args
, bởi vì args
có thể dễ dàng bị nhầm lẫn với một hàm
được định nghĩa trong mô-đun hiện tại.
Hàm
args
và Unicode không hợp lệLưu ý rằng
std::env::args
sẽ panic nếu bất kỳ đối số nào chứa Unicode không hợp lệ. Nếu chương trình của bạn cần chấp nhận các đối số chứa Unicode không hợp lệ, hãy sử dụngstd::env::args_os
thay thế. Hàm đó trả về một iterator tạo ra giá trịOsString
thay vì giá trịString
. Chúng ta đã chọn sử dụngstd::env::args
ở đây để đơn giản hóa bởi vì giá trịOsString
khác nhau trên mỗi nền tảng và phức tạp hơn để làm việc so với giá trịString
.
Ở dòng đầu tiên của main
, chúng ta gọi env::args
, và ngay lập tức sử dụng
collect
để chuyển iterator thành một vector chứa tất cả các giá trị được tạo
ra bởi iterator. Chúng ta có thể sử dụng hàm collect
để tạo nhiều loại bộ sưu
tập khác nhau, vì vậy chúng ta chú thích rõ ràng kiểu của args
để chỉ định
rằng chúng ta muốn một vector của các chuỗi. Mặc dù bạn rất hiếm khi cần phải
chú thích kiểu trong Rust, collect
là một hàm mà bạn thường cần phải chú thích
bởi vì Rust không thể suy luận được loại bộ sưu tập mà bạn muốn.
Cuối cùng, chúng ta in vector sử dụng macro debug. Hãy thử chạy mã này đầu tiên không có đối số và sau đó với hai đối số:
$ 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",
]
Lưu ý rằng giá trị đầu tiên trong vector là "target/debug/minigrep"
, đó là tên
của binary của chúng ta. Điều này phù hợp với hành vi của danh sách đối số trong
C, cho phép các chương trình sử dụng tên mà chúng được gọi trong quá trình thực
thi. Thường rất thuận tiện để có quyền truy cập vào tên chương trình trong
trường hợp bạn muốn in nó trong các thông báo hoặc thay đổi hành vi của chương
trình dựa trên alias dòng lệnh nào được sử dụng để gọi chương trình. Nhưng cho
mục đích của chương này, chúng ta sẽ bỏ qua nó và chỉ lưu hai đối số mà chúng ta
cần.
Lưu giá trị đối số trong các biến
Chương trình hiện tại có thể truy cập các giá trị được chỉ định làm đối số dòng lệnh. Bây giờ chúng ta cần lưu giá trị của hai đối số trong các biến để chúng ta có thể sử dụng các giá trị đó trong phần còn lại của chương trình. Chúng ta làm điều đó trong 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}");
}
Như chúng ta đã thấy khi in vector, tên của chương trình chiếm vị trí giá trị
đầu tiên trong vector tại args[0]
, vì vậy chúng ta bắt đầu đối số từ chỉ
mục 1. Đối số đầu tiên mà minigrep
nhận là chuỗi chúng ta đang tìm kiếm, vì
vậy chúng ta đặt một tham chiếu đến đối số đầu tiên trong biến query
. Đối số
thứ hai sẽ là đường dẫn tệp, vì vậy chúng ta đặt một tham chiếu đến đối số thứ
hai trong biến file_path
.
Chúng ta tạm thời in các giá trị của những biến này để chứng minh rằng mã đang
hoạt động như chúng ta mong muốn. Hãy chạy chương trình này một lần nữa với các
đối số test
và 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
Tuyệt vời, chương trình đang hoạt động! Giá trị của các đối số mà chúng ta cần đang được lưu vào các biến đúng. Sau này, chúng ta sẽ thêm xử lý lỗi để đối phó với một số tình huống lỗi tiềm ẩn, chẳng hạn như khi người dùng không cung cấp đối số; hiện tại, chúng ta sẽ bỏ qua tình huống đó và làm việc để thêm khả năng đọc tệp thay vào đó.