Kommandozeilenargumente entgegennehmen
Lass uns ein neues Projekt, wie immer, mit cargo new
erstellen. Wir werden
unser Projekt minigrep
nennen, um es vom grep
-Werkzeug zu unterscheiden,
das du vielleicht schon auf deinem System hast.
$ cargo new minigrep
Created binary (application) `minigrep` project
$ cd minigrep
Die erste Aufgabe besteht darin, minigrep
dazu zu bringen, seine beiden
Kommandozeilenargumente entgegennehmen: Den Dateipfad und eine Zeichenkette,
nach der gesucht werden soll. Das heißt, wir wollen in der Lage sein, unser
Programm mit cargo run
, einer zu suchenden Zeichenkette und einem Pfad zu
einer Datei, in der gesucht werden soll, auszuführen:
$ cargo run -- searchstring example-filename.txt
Im Moment kann das von cargo new
generierte Programm die Argumente, die wir
ihm geben, nicht verarbeiten. Einige vorhandene Bibliotheken auf
crates.io können beim Schreiben eines Programms, das
Kommandozeilenargumente akzeptiert, helfen, aber da du dieses Konzept gerade
erst erlernst, sollten wir diese Fähigkeit selbst implementieren.
Lesen der Argumentwerte
Um minigrep
in die Lage zu versetzen, die Werte der Kommandozeilenargumente
zu lesen, die wir ihm übergeben, benötigen wir die Funktion std::env::args
,
die in der Standardbibliothek von Rust bereitgestellt wird. Diese Funktion gibt
einen Iterator der Befehlszeilenargumente zurück, die an minigrep
übergeben
wurden. Iteratoren werden wir in Kapitel 13 ausführlich behandeln. Im
Moment brauchst du nur zwei Details über Iteratoren zu wissen: Iteratoren
erzeugen eine Reihe von Werten und wir können die Methode collect
auf einem
Iterator aufrufen, um ihn in eine Kollektion, z.B. einen Vektor, zu verwandeln,
der alle Elemente enthält, die der Iterator erzeugt.
Der Code in Codeblock 12-1 ermöglicht deinem minigrep
-Programm, alle ihm
übergebenen Befehlszeilenargumente zu lesen und die Werte dann in einem Vektor
zu sammeln.
Dateiname: src/main.rs
use std::env; fn main() { let args: Vec<String> = env::args().collect(); dbg!(args); }
Zuerst bringen wir das Modul std::env
mit einer use
-Anweisung in den
Gültigkeitsbereich, damit wir seine Funktion args
verwenden können. Beachte,
dass die Funktion std::env::args
in zwei Modulebenen verschachtelt ist. Wie
wir in Kapitel 7 besprochen haben, haben wir in Fällen, in
denen die gewünschte Funktion in mehreren Modulebenen verschachtelt ist, das
die Funktion enthaltende Modul in den Gültigkeitsbereich gebracht, anstatt nur
die Funktion selbst zu importieren. Auf diese Weise können wir leicht andere
Funktionen aus std::env
verwenden. Es ist auch nicht so vieldeutig wie beim
Importieren von use std::env::args
und dem anschließenden Aufrufen der
Funktion nur mit args
, weil args
leicht mit einer Funktion verwechselt
werden könnte, die im aktuellen Modul definiert ist.
Die
args
-Funktion und ungültiger UnicodeBeachte, dass
std::env::args
abstürzt, wenn ein Argument einen ungültigen Unicode enthält. Wenn dein Programm Argumente mit ungültigem Unicode akzeptieren muss, verwende stattdessenstd::env::args_os
. Diese Funktion gibt einen Iterator zurück, derOsString
-Werte anstelle vonString
-Werten erzeugt. Wir haben uns hier aus Gründen der Einfachheit für die Verwendung vonstd::env::args
entschieden, weilOsString
-Werte sich je nach Plattform unterscheiden und die Arbeit mit ihnen komplexer ist als mitString
-Werten.
In der ersten Zeile von main
rufen wir env::args
auf und wir verwenden
sofort collect
, um den Iterator in einen Vektor zu verwandeln, der alle vom
Iterator erzeugten Werte enthält. Wir können die Funktion collect
verwenden,
um viele Arten von Kollektionen zu erstellen, also vermerken wir explizit den
Typ von args
, um anzugeben, dass wir einen Vektor mit Zeichenketten wollen.
Obwohl du in Rust nur sehr selten Typen mit Annotationen versehen musst, ist
collect
eine Funktion, die du häufig mit Annotationen versehen musst, da Rust
nicht in der Lage ist, auf die Art der gewünschten Kollektion zu schließen.
Zum Schluss geben wir den Vektor mit dem Debug-Makro aus. Versuchen wir, den Code zuerst ohne Argumente und dann mit zwei Argumenten laufen zu lassen:
$ 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:5] args = [
"target/debug/minigrep",
]
$ cargo run -- Nadel Heuhaufen
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 1.57s
Running `target/debug/minigrep Nadel Heuhaufen`
[src/main.rs:5:5] args = [
"target/debug/minigrep",
"Nadel",
"Heuhaufen",
]
Beachte, dass der erste Wert im Vektor "target/debug/minigrep"
ist, was der
Name unserer Binärdatei ist. Dies entspricht dem Verhalten der Argumentliste in
C, sodass Programme bei ihrer Ausführung den Namen verwenden können, unter dem
sie aufgerufen wurden. Es ist oft praktisch, Zugriff auf den Programmnamen zu
haben, falls du ihn in Meldungen ausgeben oder das Verhalten des Programms
ändern möchtest, je nachdem, welcher Befehlszeilen-Alias zum Aufruf des
Programms verwendet wurde. Aber für die Zwecke dieses Kapitels ignorieren wir
ihn und speichern nur die beiden Argumente, die wir brauchen.
Speichern der Argumentwerte in Variablen
Das Programm ist derzeit in der Lage, auf die als Kommandozeilenargumente angegebenen Werte zuzugreifen. Jetzt müssen wir die Werte der beiden Argumente in Variablen speichern, damit wir die Werte im restlichen Programm verwenden können. Das tun wir in Codeblock 12-2.
Dateiname: 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!("Suche nach {query}");
println!("In Datei {file_path}");
}
Wie wir gesehen haben, als wir den Vektor ausgegeben haben, nimmt der
Programmname den ersten Wert im Vektor bei args[0]
ein, also beginnen wir
beim Index 1
. Das erste Argument, das minigrep
annimmt, ist die
Zeichenkette, nach der wir suchen, also setzen wir eine Referenz auf das erste
Argument in die Variable query
. Das zweite Argument wird der Dateipfad sein,
also setzen wir eine Referenz auf das zweite Argument in die Variable
file_path
.
Wir geben vorübergehend die Werte dieser Variablen aus, um zu belegen, dass der
Code so funktioniert, wie wir es beabsichtigen. Lassen wir dieses Programm mit
den Argumenten test
und sample.txt
noch einmal laufen:
$ 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`
Suche nach test
In Datei sample.txt
Toll, das Programm funktioniert! Die Werte der Argumente, die wir brauchen, werden in den richtigen Variablen gespeichert. Später fügen wir eine Fehlerbehandlung hinzu, um mit bestimmten potentiellen Fehlersituationen umzugehen, z.B. wenn der Benutzer keine Argumente angibt; für den Moment ignorieren wir diese Situation und arbeiten stattdessen daran, die Datei-Lesefunktion hinzuzufügen.