Zum Inhalt springen

Kommandozeilenargumente in Rust

Viele kleine Übungsprojekte, die sich lohnen – etwa eine To-do-Liste oder ein Wortzähler –, sollen ihre Eingaben von der Kommandozeile bekommen, statt feste Werte im Quellcode zu haben. Auf dieser Seite lernst du die Grundlagen.

use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
println!("{:?}", args);
}

Angenommen, du kompilierst das zu einem Programm namens greet und rufst es so auf:

Terminal window
./greet Alice

Dann siehst du:

["greet", "Alice"]

Das erste Element ist immer der Name des Programms selbst. Die Argumente, die du tatsächlich eingegeben hast, beginnen bei Index 1.

use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
println!("Verwendung: greet <name>");
return;
}
let name = &args[1];
println!("Hallo, {name}!");
}

Mit cargo run -- Alice bekommst du:

Hallo, Alice!

Startest du das Programm ohne Argumente, siehst du statt eines Absturzes den Hinweis zur Verwendung. Weil du args.len() prüfst, bevor du auf einen Index zugreifst, gibt es keinen Panic, wenn jemand vergisst, etwas zu übergeben.

Jedes Argument kommt als String an. Deshalb brauchst du oft .parse() (von der Seite Typumwandlung), um daraus eine Zahl zu machen:

use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
println!("Verwendung: square <zahl>");
return;
}
match args[1].parse::<i32>() {
Ok(n) => println!("{}", n * n),
Err(_) => println!("Das ist keine gültige Zahl"),
}
}

Ein Flag ist ein Argument, das etwas einschaltet, zum Beispiel --shout. Bevor du zu einem Crate greifst, lohnt sich ein Blick darauf, wie du so etwas selbst auswertest. Der Trick, der das leicht testbar macht: Steck die Logik in eine Funktion, die die Argumente als Slice &[String] bekommt. Dann kann main die echten Argumente übergeben, und beim Ausprobieren gibst du einfach eine selbst gebaute Liste hinein:

fn run(args: &[String]) {
let mut shout = false;
let mut names = Vec::new();
// Index 0 überspringen, das ist der Programmname
for arg in &args[1..] {
if arg == "--shout" {
shout = true;
} else {
names.push(arg);
}
}
if names.is_empty() {
println!("Verwendung: greet [--shout] <name>...");
return;
}
for name in names {
let line = format!("Hallo, {name}!");
if shout {
println!("{}", line.to_uppercase());
} else {
println!("{line}");
}
}
}
fn main() {
// In einem echten Programm würdest du schreiben:
// let args: Vec<String> = std::env::args().collect();
// Hier bauen wir die Liste von Hand, damit es auch im Playground funktioniert.
let args = vec![
String::from("greet"),
String::from("--shout"),
String::from("Ana"),
String::from("Ben"),
];
run(&args);
}

Ausgabe:

HALLO, ANA!
HALLO, BEN!
▶ Selbst ausprobieren

Ein paar Dinge, die dir auffallen sollten:

  • &args[1..] sind alle Argumente außer dem Programmnamen. Das Flag darf überall stehen: greet Ana --shout Ben funktioniert auch.
  • Nimm --shout aus der Liste und starte das Programm noch einmal: Dann bekommst du die normalen Begrüßungen. Entfernst du auch die Namen, siehst du den Hinweis zur Verwendung.
  • Weil es run egal ist, woher seine Liste kommt, musst du in main nur eine Zeile ändern, um std::env::args().collect() einzusetzen.

Für ein oder zwei Flags reicht das völlig. Willst du aber auch Kurzformen (-s), Werte nach einem Flag (--count 3) und eine --help-Ausgabe, wächst die handgeschriebene Schleife schnell. Das ist der Moment, um auf clap umzusteigen.

Sobald du Flags (--verbose), optionale Werte oder eine hilfreiche Ausgabe für --help brauchst, wird das Hantieren mit Indizes in args schnell unübersichtlich. Die übliche Lösung ist das Crate clap (auf Englisch). Du fügst es so hinzu:

Terminal window
cargo add clap --features derive
use clap::Parser;
#[derive(Parser)]
struct Args {
name: String,
#[arg(short, long, default_value_t = 1)]
count: u32,
}
fn main() {
let args = Args::parse();
for _ in 0..args.count {
println!("Hallo, {}!", args.name);
}
}

Damit bekommst du das Auswerten der Argumente, ihre Prüfung und eine funktionierende --help-Ausgabe – alles automatisch aus der Struct-Definition erzeugt. Für schnelle Skripte brauchst du clap nicht. Aber für alles, was du öfter als einmal benutzt, spart es dir eine Menge Handarbeit.

Der Playground kann keine echten Argumente übergeben. Deshalb starten beide Übungen mit einem selbst gebauten Vec<String>, der genauso aussieht wie das, was env::args().collect() dir liefern würde. Auf deinem eigenen Rechner ersetzt du ihn durch die echten Argumente und probierst cargo run -- ... aus.

Du hast vec!["notes", "add", "Milch kaufen"] (als Strings). Gib aus, wie viele Argumente der Benutzer eingegeben hat (ohne den Programmnamen), und danach jedes Argument mit seiner Position, beginnend bei 1.

Tipp: Die Anzahl ist args.len() - 1. Eine Schleife for i in 1..args.len() liefert dir jeden Index nach dem Programmnamen.

Lösung anzeigen
fn main() {
// stell dir vor, das kommt von std::env::args().collect()
let args = vec![
String::from("notes"),
String::from("add"),
String::from("Milch kaufen"),
];
println!("Du hast {} Argumente übergeben", args.len() - 1);
for i in 1..args.len() {
println!("{i}: {}", args[i]);
}
}

Ausgabe:

Du hast 2 Argumente übergeben
1: add
2: Milch kaufen
▶ Selbst ausprobieren

"Milch kaufen" zählt als ein Argument: Auf einer echten Kommandozeile schreibst du es in Anführungszeichen (cargo run -- add "Milch kaufen"), und die Shell übergibt es als einen einzigen String. Index 0 ist weiterhin der Programmname, deshalb beginnt die Schleife bei 1.

Du hast vec!["sum", "4", "10", "x", "6"]. Addiere jedes Argument, das eine gültige Zahl ist, und gib für jedes andere eine Warnung aus. Zum Schluss gibst du die Summe aus.

Tipp: Lauf mit einer Schleife über &args[1..] und nimm match arg.parse::<i32>(), wie in Ein Argument in eine Zahl umwandeln. Leg let mut total = 0; außerhalb der Schleife an.

Lösung anzeigen
fn main() {
let args = vec![
String::from("sum"),
String::from("4"),
String::from("10"),
String::from("x"),
String::from("6"),
];
let mut total = 0;
for arg in &args[1..] {
match arg.parse::<i32>() {
Ok(n) => total += n,
Err(_) => println!("Überspringe '{arg}': keine Zahl"),
}
}
println!("Summe: {total}");
}

Ausgabe:

Überspringe 'x': keine Zahl
Summe: 20
▶ Selbst ausprobieren

Jedes Argument kommt als Text an, sogar "4". Deshalb muss jedes einzeln geparst werden. Ungültige Eingaben mit einer Meldung zu überspringen ist freundlicher als ein Absturz. Du könntest auch beim ersten ungültigen Argument mit return aufhören, wenn das besser zu deinem Programm passt.

  • std::env::args().collect() liefert dir einen Vec<String> mit den Kommandozeilenargumenten. Index 0 ist immer der Programmname.
  • Prüfe args.len(), bevor du auf einen Index zugreifst. Dann löst ein fehlendes Argument keinen Panic aus.
  • .parse() wandelt ein String-Argument in eine Zahl um – genau wie anderswo auch.
  • Steck das Auswerten der Argumente in eine Funktion, die &[String] bekommt. Dann kannst du sie mit einer selbst gebauten Liste ausprobieren.
  • Für mehr als ein paar einfache Argumente ist das Crate clap die übliche, bewährte Wahl.

Kurzer Check

1. Was ist das erste Element des Vec, den du mit env::args().collect() bekommst?

2. Warum solltest du args.len() prüfen, bevor du auf einen Index in args zugreifst?

3. Was bietet dir das Crate clap, was der direkte Zugriff auf args nicht bietet?

Punkte: 0 / 3