Einführung
Ein Kommandozeilenprogramm empfängt Werte nach dem Namen der ausführbaren Datei, schreibt normale Ergebnisse in die Standardausgabe und meldet Fehler über die Standardfehlerausgabe sowie einen Exit-Status ungleich null. Anhand dieser Prozessgrenzen können Menschen und Skripte Erfolg und Fehler unterscheiden.
Sie vervollständigen ein kleines Textsuchprogramm. Die Suchfunktion ist bereits im Bibliotheksquelltext des Pakets vorbereitet. Daher konzentriert sich jeder Schritt auf das Parsen von Argumenten, das Einlesen einer Datei, das Verhalten bei keinen Treffern und die abschließende Kommunikation des Prozesses.
Kommandozeilenargumente parsen
In diesem Schritt validieren und extrahieren Sie eine Suchanfrage sowie einen Dateipfad aus den Prozessargumenten.
Das Projekt befindet sich unter /home/labex/project/mini-search. src/main.rs enthält das Kommandozeilen-Binärprogramm, während src/lib.rs vorbereitete, wiederverwendbare Suchlogik für einen späteren Schritt enthält. Wechseln Sie in das Projektverzeichnis und öffnen Sie den Quelltext des Binärprogramms:
cd /home/labex/project/mini-search
nano src/main.rs
env::args() liefert jedes Argument als String, einschließlich des Pfads zur ausführbaren Datei am Index null. Die vorbereitete Funktion main sammelt diese Werte in einem Vektor. Anschließend übergibt sie &args an den Parameter args: &[String].
Der Typ &[String] ist ein Slice aus String-Werten: ein schreibgeschützter, ausgeliehener Ausschnitt aus den Elementen des Vektors. Er folgt demselben Ausleihprinzip wie &str, betrachtet jedoch eine Folge von String-Elementen statt Textbytes. Der Vektor bleibt im Besitz von main, während run seine Elemente liest.
Der Datenfluss sieht so aus:
shell words → env::args() → Vec<String> in main → borrowed &[String] in run
Ersetzen Sie den Platzhalter Err(...) innerhalb von run durch:
if args.len() != 3 {
return Err(String::from("usage: mini-search <query> <file>"));
}
let query = args[1].clone();
let path = args[2].clone();
Ok(vec![format!("Query: {query}"), format!("File: {path}")])
Genau drei Einträge bedeuten: der Name der ausführbaren Datei plus zwei Benutzerargumente. Das ausdrückliche return Err(...) beendet run sofort, wenn diese Form nicht stimmt. Dies ist eine vorzeitige Rückgabe und unterscheidet sich von den Rückgaben über den letzten Ausdruck, die Sie im Modul zu Funktionen verwendet haben: Der nachfolgende Code wird nur ausgeführt, wenn die Anzahl der Argumente gültig ist.
Durch den Zugriff per Index auf das ausgeliehene Slice erhalten Sie ausgeliehene Strings. Die beiden Aufrufe von clone erzeugen absichtlich eigene Kopien nur der Suchanfrage und des Pfads, damit run sie im weiteren Verlauf als lokale Werte im Besitz der Funktion verwalten kann. Diese Entscheidung betrifft gezielt den Besitz und ist keine allgemeine Lösung für Verschiebefehler. Das Makro vec![first, second] erstellt anschließend einen Vektor mit zwei Elementen, und Ok gibt vorübergehend diese beiden Prüfzeilen zurück.
Speichern Sie mit Ctrl+O, drücken Sie Enter und beenden Sie nano mit Ctrl+X. Führen Sie das Programm mit zwei Argumenten aus:
cargo run --quiet -- rust data/notes.txt
Das erste --quiet reduziert die eigenen Meldungen von Cargo. Das separate -- weist Cargo an, die Verarbeitung von Optionen zu beenden. Alles danach wird an Ihr Programm übergeben.
Query: rust
File: data/notes.txt
Damit ist bestätigt, dass die Argumente an den erwarteten Positionen angekommen sind.
Datei einlesen und Bibliothekslogik aufrufen
In diesem Schritt ersetzen Sie das vorläufige Prüfergebnis durch das tatsächliche Einlesen der Datei und die Ausgabe der Suchergebnisse.
Öffnen Sie den Quelltext des Binärprogramms:
nano src/main.rs
Fügen Sie unter use std::env; diese Importe hinzu:
use std::fs;
use mini_search::find_lines;
Der Paketname mini-search wird zum Rust-Crate-Namen mini_search. Das vorbereitete pub vor find_lines macht diese Funktion außerhalb des Bibliotheks-Crates zugänglich. Durch den Import kann das Binärprogramm die öffentliche Funktion aus src/lib.rs aufrufen. Die Suchlogik bleibt dort getrennt von der prozessspezifischen Verarbeitung der Argumente und kann dadurch wiederverwendet werden. Dies ist ein kleiner Ausblick auf das Modell aus Bibliothek, Binärprogramm und Sichtbarkeit, das im späteren Modul zu Modulen vollständig behandelt wird.
Ersetzen Sie die vorläufige Zeile Ok(vec![...]) durch:
let contents = fs::read_to_string(&path)
.map_err(|error| format!("could not read {path}: {error}"))?;
Ok(find_lines(&query, &contents))
Die Lesefehler enthalten weiterhin den angeforderten Pfad und werden mit ? weitergegeben. Bei Erfolg leiht das Binärprogramm die Suchanfrage und den Dateiinhalt aus, während die Bibliothek die passenden Zeilen im eigenen Besitz zurückgibt.
Speichern und beenden Sie den Editor. Prüfen und starten Sie das Programm anschließend:
cargo check
cargo run --quiet -- rust data/notes.txt
Rust makes ownership explicit.
Cargo builds Rust packages.
Rust tools help beginners.
Die vorbereitete Ok-Verzweigung in main gibt normale Suchergebnisse auf der Standardausgabe aus.
Eine leere Suche in einen hilfreichen Fehler umwandeln
In diesem Schritt unterscheiden Sie eine erfolgreiche Suche mit Treffern von einer gültigen Suche, die nichts gefunden hat.
Öffnen Sie den Quelltext:
nano src/main.rs
Ersetzen Sie Ok(find_lines(&query, &contents)) durch:
let matches = find_lines(&query, &contents);
if matches.is_empty() {
return Err(format!("no lines matched '{query}'"));
}
Ok(matches)
Ein leerer Vektor ist zwar erkennbar, aber das Kommando ist hilfreicher, wenn es diesen Ausgang ausdrücklich erklärt. Speichern und beenden Sie den Editor. Suchen Sie anschließend nach einem Begriff, der nicht vorkommt:
cargo run --quiet -- python data/notes.txt
no lines matched 'python'
In dieser Zwischenstufe gibt die vorbereitete Err-Verzweigung die Fehlermeldung noch auf der Standardausgabe aus und beendet das Programm erfolgreich. Im nächsten Schritt geben Sie Fehler über das korrekte Prozessverhalten aus.
Fehler über stderr ausgeben und mit einem Status ungleich null beenden
In diesem Schritt vervollständigen Sie die Kommandozeilengrenze, indem Sie normale Ausgaben und Fehler voneinander trennen.
Die Standardausgabe, kurz stdout, enthält die angeforderten Ergebnisse. Die Standardfehlerausgabe, kurz stderr, enthält unabhängig davon Diagnosemeldungen. Ein Exit-Status von null bedeutet Erfolg; ein Status ungleich null bedeutet einen Fehler. Öffnen Sie den Quelltext des Binärprogramms:
nano src/main.rs
Fügen Sie unter den vorhandenen Importen aus der Standardbibliothek diesen Import hinzu:
use std::process;
Ersetzen Sie die einzeilige Err(error)-Verzweigung durch:
Err(error) => {
eprintln!("{error}");
process::exit(1);
}
eprintln! schreibt eine Zeile nach stderr. process::exit(1) beendet den Prozess sofort mit dem Status eins. Speichern und beenden Sie den Editor. Erstellen Sie anschließend einmal das Binärprogramm, damit Sie es direkt ausführen können, ohne dass Cargo eine eigene Fehlermeldung hinzufügt:
cargo build --quiet
./target/debug/mini-search python data/notes.txt
Die Diagnose bleibt:
no lines matched 'python'
Geben Sie unmittelbar den Status des vorherigen Befehls aus:
echo $?
echo gibt seine Argumente aus, und die Shell ersetzt $? durch den Exit-Status des zuletzt ausgeführten Befehls:
1
Dieselbe Prozessgrenze gilt nun auch für eine ungültige Verwendung und für fehlende Dateien. Wenn Sie ./target/debug/mini-search ohne Argumente ausführen, wird die Verwendungsmeldung ausgegeben. Ein Pfad wie data/missing.txt erzeugt eine Lesefehlerdiagnose. Beide Meldungen werden ausschließlich nach stderr geschrieben und beenden das Programm mit Status eins. Eine Suche mit Treffern schreibt dagegen die Ergebnisse nach stdout und beendet das Programm mit Status null.
Zusammenfassung
Sie haben Kommandozeilenargumente gesammelt und validiert, Cargo mit dem Trennzeichen -- verwendet, die Suchlogik in einer vorbereiteten Bibliotheksgrenze belassen, die angeforderte Datei eingelesen und das übliche Verhalten von stdout, stderr und Exit-Status für einen Erfolg sowie drei Fehlerfälle umgesetzt.


