Acceptarea argumentelor liniei de comandă
Să creăm un nou proiect folosind, ca întotdeauna, cargo new
. Vom numi proiectul nostru minigrep
pentru a-l diferenția de instrumentul grep
pe care s-ar putea să îl ai deja pe sistemul tău.
$ cargo new minigrep
Created binary (application) `minigrep` project
$ cd minigrep
Prima sarcină este să configurăm minigrep
să accepte cei doi parametri de linie de comandă: calea fișierului și un string pentru căutare. Astfel, dorim să putem executa programul nostru cu cargo run
, urmat de două liniuțe pentru a indica faptul că argumentele ulterioare sunt destinate programului nostru și nu pentru cargo
, un string de căutat, și o cale către un fișier în care să efectuăm căutarea, ca în exemplul următor:
$ cargo run -- searchstring example-filename.txt
Deocamdată, programul generat de cargo new
nu este capabil să proceseze argumentele pe care i le transmitem. Există biblioteci disponibile pe crates.io care ne-ar putea facilita scrierea unui program capabil să accepte argumente din linia de comandă, dar deoarece suntem în proces de învățare a acestui concept, să încercăm să implementăm această funcționalitate pe cont propriu.
Citirea valorilor argumentelor
Pentru ca minigrep
să poată citi valorile argumentelor de pe linia de comandă, e necesar să folosim funcția std::env::args
din biblioteca standard a Rust. Aceasta returnează un iterator cu argumentele de linia de comandă primite de minigrep
. Detalii complete despre iteratori vor fi disponibile în Capitolul 13. În prezent, trebuie să reținem două aspecte despre iteratori: aceștia generează o serie de valori și putem folosi metoda collect
pentru a transforma iteratorul într-o colecție, cum ar fi un array, care include toate elementele produse de acesta.
Codul din Listarea 12-1 îi va permite programului minigrep
să citească argumentele de pe linia de comandă care îi sunt transmise și să le transfere într-un array.
Numele fișierului: src/main.rs
use std::env; fn main() { let args: Vec<String> = env::args().collect(); dbg!(args); }
Listarea 12-1: Colectarea argumentelor transmise pe linia de comandă într-un array și afișarea acestora
Înainte de toate, importăm modulul std::env
în contextul nostru cu ajutorul unei instrucțiuni use
, pentru ca să putem utiliza funcția args
din acesta. Se remarcă faptul că funcția std::env::args
se află în cadrul a două module. Așa cum s-a discutat în Capitolul 7, atunci când funcția dorită este situată în mai multe module, optăm să importăm modulul părinte în locul unei funcții anume. Prin această abordare, putem accesa alte funcții din std::env
cu mai multă ușurință. De asemenea, evităm ambiguitatea care ar apărea prin adăugarea use std::env::args
și apoi apelarea funcției simplu cu args
, ceea ce ar putea fi confundat cu o funcție definită local.
Funcția
args
și Unicode invalidEste important de reținut că
std::env::args
va genera panică dacă orice argument conține Unicode invalid. Dacă este necesar ca programul tău să accepte argumente care includ Unicode invalid, atunci ar trebui utilizată funcțiastd::env::args_os
. Această funcție oferă un iterator ce generează valori de tipOsString
în loc deString
. Am optat pentru utilizarea luistd::env::args
aici datorită simplității, deoarece valorile de tipOsString
variază în funcție de platformă și sunt mai complicate de manevrat decât valorile de tipString
.
La prima linie a funcției main
, invocăm env::args
, apoi utilizăm imediat collect
pentru a converti iteratorul într-un vector care conține toate valorile generate de iterator. Putem apela funcția collect
pentru a crea diferite tipuri de colecții, motiv pentru care specificăm în mod explicit tipul pentru args
, indicând că dorim un vector de string-uri. Chiar dacă în Rust rar este necesar să adnotăm tipurile, când vine vorba de collect
, adesea este nevoie de acest lucru deoarece aici Rust nu îți poate infera tipul de colecție dorit.
În final, afișăm vectorul folosind macro-ul de debug. Să încercăm să executăm codul mai întâi fără argumente, apoi cu două argumente:
$ cargo run
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 0.61s
Running `target/debug/minigrep`
[src/main.rs:5] args = [
"target/debug/minigrep",
]
$ cargo run -- needle haystack
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 1.57s
Running `target/debug/minigrep needle haystack`
[src/main.rs:5] args = [
"target/debug/minigrep",
"needle",
"haystack",
]
Observăm că prima valoare din vector este "target/debug/minigrep"
, numele executabilului nostru. Aceasta este în acord cu comportamentul listei de argumente din C, care permite programelor să utilizeze numele sub care au fost invocate în cursul execuției. Oferirea accesului la numele programului poate fi utilă dacă dorim să îl afișăm în mesaje sau să modificăm comportamentul programului în funcție de aliasul de la linia de comandă folosit pentru invocare. Totuși, pentru nevoile acestui capitol, vom ignora acest aspect și vom salva doar cele două argumente necesare.
Salvând valorile argumentelor în variabile
Programul nostru este acum capabil să acceseze valorile specificate ca argumente ale liniei de comandă. Trebuie să salvăm acum valorile celor doi argumente în variabile, pentru a putea utiliza aceste valori pe parcursul restului programului. Această etapă este realizată în Listarea 12-2.
Numele fișierului: src/main.rs
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);
}
Listarea 12-2: Creăm variabile pentru memorarea argumentului de căutare și argumentului cu calea fișierului
Așa cum am observat când am afișat vectorul, numele programului este stocat în prima valoare a vectorului la args[0]
, deci ne apucăm să lucram cu argumentele începând de la indexul 1
. Primul argument pe care minigrep
îl solicită este string-ul pe care dorim să îl căutăm, așa că atribuim o referință către primul argument variabilei query
. Pentru al doilea argument, care reprezintă calea fișierului, atribuim o referință către al doilea argument variabilei file_path
.
Printăm temporar valorile acestor variabile pentru a ne asigura că programul funcționează conform intențiilor noastre. Să executăm acest program din nou, de data aceasta cu argumentele test
și sample.txt
:
$ cargo run -- test sample.txt
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 0.0s
Running `target/debug/minigrep test sample.txt`
Searching for test
In file sample.txt
Minunat, programul funcționează corect! Valorile argumentelor necesare sunt acum salvate în variabilele potrivite. Ulterior, vom adăuga gestionarea erorilor pentru a face față situațiilor eronate, cum ar fi cazul în care utilizatorul nu furnizează niciun argument; deocamdată, vom trece peste astfel de situații și ne vom concentra asupra implementării funcționalității de citire a fișierelor.