Wie greife ich auf Befehlszeilenparameter zu?

153

Das Rust-Tutorial erklärt nicht, wie Parameter von der Befehlszeile übernommen werden. fn main()wird in allen Beispielen nur mit einer leeren Parameterliste angezeigt.

Wie kann über Befehlszeilenparameter korrekt zugegriffen werden main?

Shutefan
quelle

Antworten:

168

Sie können auf die Befehlszeilenargumente zugreifen, indem Sie die Funktionen std::env::argsoder std::env::args_osverwenden. Beide Funktionen geben einen Iterator über die Argumente zurück. Ersteres iteriert über Strings (die leicht zu bearbeiten sind), gerät jedoch in Panik, wenn eines der Argumente kein gültiger Unicode ist. Letzteres iteriert über OsStrings und gerät nie in Panik.

Beachten Sie, dass das erste Element des Iterators der Name des Programms selbst ist (dies ist eine Konvention in allen wichtigen Betriebssystemen), sodass das erste Argument tatsächlich das zweite iterierte Element ist.

Eine einfache Möglichkeit, mit dem Ergebnis von umzugehen, argsbesteht darin, es in Folgendes umzuwandeln Vec:

use std::env;

fn main() {
    let args: Vec<_> = env::args().collect();
    if args.len() > 1 {
        println!("The first argument is {}", args[1]);
    }
}

Sie können die gesamte Standard-Iterator-Toolbox verwenden , um mit diesen Argumenten zu arbeiten. Um beispielsweise nur das erste Argument abzurufen:

use std::env;

fn main() {
    if let Some(arg1) = env::args().nth(1) {
        println!("The first argument is {}", arg1);
    }
}

Auf crates.io finden Sie Bibliotheken zum Parsen von Befehlszeilenargumenten:

  • docopt : Sie schreiben einfach die Hilfemeldung und der Parsing-Code wird für Sie generiert.
  • clap : Sie beschreiben die Optionen, die Sie mithilfe einer fließenden API analysieren möchten. Schneller als docopt und gibt Ihnen mehr Kontrolle.
  • getopts : Port der beliebten C-Bibliothek. Untere Ebene und noch mehr Kontrolle.
  • structopt : Auf Klatsch gebaut, ist es noch ergonomischer zu bedienen.
Barjak
quelle
2
Auch mit Rost 0.8 sollten Sie nurprintln(args[0])
Leo Correa
6
Die obigen Kommentare (von @LeoCorrea / @ S4M) bezogen sich auf eine alte Version der Antwort; Die aktuelle Version der Antwort enthält die aktuellsten Informationen.
Nickolay
22

Docopt ist auch für Rust verfügbar, das aus einer Verwendungszeichenfolge einen Parser für Sie generiert. Als Bonus in Rust kann ein Makro verwendet werden, um die Struktur automatisch zu generieren und eine typbasierte Dekodierung durchzuführen:

docopt!(Args, "
Usage: cp [-a] SOURCE DEST
       cp [-a] SOURCE... DIR

Options:
    -a, --archive  Copy everything.
")

Und Sie können die Argumente bekommen mit:

let args: Args = Args::docopt().decode().unwrap_or_else(|e| e.exit());

Die README-Datei und die Dokumentation enthalten zahlreiche vollständige Arbeitsbeispiele.

Haftungsausschluss: Ich bin einer der Autoren dieser Bibliothek.

BurntSushi5
quelle
10

Getopts fühlte sich für mich immer zu niedrig an und docopt.rs war zu viel Magie. Ich möchte etwas Explizites und Unkompliziertes, das immer noch alle Funktionen bietet, wenn ich sie brauche.

Hier bietet sich das Klatschen an.
Es fühlt sich ein bisschen wie Argparse aus Python an. Hier ist ein Beispiel, wie es aussieht:

let matches = App::new("myapp")
                      .version("1.0")
                      .author("Kevin K. <[email protected]>")
                      .about("Does awesome things")
                      .arg(Arg::with_name("CONFIG")
                           .short("c")
                           .long("config")
                           .help("Sets a custom config file")
                           .takes_value(true))
                      .arg(Arg::with_name("INPUT")
                           .help("Sets the input file to use")
                           .required(true)
                           .index(1))
                      .arg(Arg::with_name("debug")
                           .short("d")
                           .multiple(true)
                           .help("Sets the level of debugging information"))
                      .get_matches();

Sie können wie folgt auf Ihre Parameter zugreifen:

println!("Using input file: {}", matches.value_of("INPUT").unwrap());

// Gets a value for config if supplied by user, or defaults to "default.conf"
let config = matches.value_of("CONFIG").unwrap_or("default.conf");
println!("Value for config: {}", config);

(Aus der offiziellen Dokumentation kopiert )

mre
quelle
1
Ich finde es gut, dass Sie mit clap-rs Ihre Schnittstelle in einer yaml-Datei definieren können. Außerdem werden wirklich gut aussehende Verwendungsanweisungen erstellt.
Chuck Wooters
Dies hat mir geholfen, meine CLI-App schnell einzurichten. Danke dir!
dimitarvp
4

Ab Version 0.8 / 0.9 wäre der korrekte Pfad zur Funktion args () ::std::os::args:

fn main() {
  let args: ~[~str] = ::std::os::args();
  println(args[0]);
}

Es scheint, dass Rust derzeit selbst mit Standard-E / A noch ziemlich volatil ist, so dass dies ziemlich schnell veraltet sein kann.

Nick
quelle
Danke für das Update! Ich denke, ich muss die akzeptierte Antwort überdenken, nachdem 1.0 veröffentlicht wurde.
Shutefan
3

Rust änderte sich wieder. os::args()wird zugunsten von abgelehnt std::args(). Ist std::args()aber kein Array, gibt es einen Iterator zurück . Sie können über die Befehlszeilenargumente iterieren, jedoch nicht mit Indizes darauf zugreifen.

http://doc.rust-lang.org/std/env/fn.args.html

Wenn Sie die Befehlszeilenargumente als Vektor von Zeichenfolgen verwenden möchten, funktioniert dies jetzt:

use std::env;
...
let args: Vec<String> = env::args().map(|s| s.into_string().unwrap()).collect();

Rost - lernen Sie, den Schmerz der Veränderung anzunehmen.

John Nagle
quelle
8
Sie müssen jetzt nur noch tun env::args().collect().
Tshepang
2

Was @barjak gesagt hat, funktioniert für Zeichenfolgen, aber wenn Sie das Argument als Zahl (in diesem Fall als Uint) benötigen, müssen Sie Folgendes konvertieren:

fn main() {
    let arg : ~[~str] = os::args();
    match uint::from_str(arg[1]){
         Some(x)=>io::println(fmt!("%u",someFunction(x))),
         None=>io::println("I need a real number")
    }
}
Calvin
quelle
2

Schauen Sie sich auch structopt an:

extern crate structopt;
#[macro_use]
extern crate structopt_derive;

use structopt::StructOpt;

#[derive(StructOpt, Debug)]
#[structopt(name = "example", about = "An example of StructOpt usage.")]
struct Opt {
    /// A flag, true if used in the command line.
    #[structopt(short = "d", long = "debug", help = "Activate debug mode")]
    debug: bool,

    /// An argument of type float, with a default value.
    #[structopt(short = "s", long = "speed", help = "Set speed", default_value = "42")]
    speed: f64,

    /// Needed parameter, the first on the command line.
    #[structopt(help = "Input file")]
    input: String,

    /// An optional parameter, will be `None` if not present on the
    /// command line.
    #[structopt(help = "Output file, stdout if not present")]
    output: Option<String>,
}

fn main() {
    let opt = Opt::from_args();
    println!("{:?}", opt);
}

https://github.com/TeXitoi/structopt

mre
quelle
1

Ab neueren Rust-Versionen (Rust> 0.10 / 11) funktioniert die Array-Syntax nicht mehr. Sie müssen die get-Methode verwenden.

[Bearbeiten] Die Array-Syntax funktioniert (wieder) in der Nacht. Sie können also zwischen dem Getter- oder dem Array-Index wählen.

use std::os;

fn main() {
  let args = os::args();
  println!("{}", args.get(1));
}

// Compile
 rustc args.rs && ./args hello-world // returns hello-world
Sturmpat
quelle
Dies ist eine veraltete Aussage. Die neuesten Rust Nightlies unterstützen die Indizierungssyntax für Vecs. Ich denke, es ist ungefähr einen Monat lang da. Siehe dieses Beispiel .
Vladimir Matveev
1

Rust hat sich seit Calvins Antwort vom Mai 2013 weiterentwickelt. Nun würde man Befehlszeilenargumente analysieren mit as_slice():

use std::os;

fn seen_arg(x: uint)
{       
    println!("you passed me {}", x);
}
fn main() {
    let args = os::args();
    let args = args.as_slice();
    let nitems = {
            if args.len() == 2 {
                    from_str::<uint>(args[1].as_slice()).unwrap()
            } else {
                    10000
            }
    };

    seen_arg(nitems);
}
Rob Latham
quelle
Nur zur Veranschaulichung: as_slice()existiert nicht mehr und &argssollte stattdessen verwendet werden.
Slava Semushin
1

Das Kapitel "No stdlib" im Rust-Buch behandelt den Zugriff auf die Befehlszeilenparameter (auf andere Weise).

// Entry point for this program
#[start]
fn start(_argc: isize, _argv: *const *const u8) -> isize {
    0
}

Das Beispiel hat auch das, #![no_std]was meiner Meinung nach bedeutet, dass die Standardbibliothek normalerweise den wahren Einstiegspunkt für Ihre Binärdatei hat und eine globale Funktion namens aufruft main(). Eine andere Möglichkeit besteht darin, die mainUnterlegscheibe mit zu deaktivieren #![no_main]. Wenn ich mich nicht irre, sagt das dem Compiler, dass Sie die volle Kontrolle darüber haben, wie Ihr Programm gestartet wird.

#![no_std]
#![no_main]

#[no_mangle] // ensure that this symbol is called `main` in the output
pub extern fn main(argc: isize, argv: *const *const u8) -> isize {
    0
}

Ich denke nicht, dass dies eine 'gute' Art ist, Dinge zu tun, wenn Sie nur Befehlszeilenargumente lesen möchten. Das std::osin anderen Antworten erwähnte Modul scheint eine viel bessere Möglichkeit zu sein, Dinge zu tun. Ich poste diese Antwort zum Zwecke der Vervollständigung.

thecoshman
quelle