Jak uzyskać dostęp do parametrów wiersza poleceń?

153

Poradnik Rust nie wyjaśnia, w jaki sposób podjąć parametrów z linii poleceń. fn main()jest wyświetlany tylko z pustą listą parametrów we wszystkich przykładach.

Jaki jest prawidłowy sposób uzyskiwania dostępu do parametrów wiersza poleceń main?

shutefan
źródło

Odpowiedzi:

168

Dostęp do argumentów wiersza poleceń można uzyskać za pomocą funkcji std::env::argslub std::env::args_os. Obie funkcje zwracają iterator po argumentach. Pierwsza iteruje po Strings (z którymi łatwo się pracuje), ale panikuje, jeśli jeden z argumentów nie jest poprawnym kodem Unicode. Ten ostatni powtarza OsStringsi i nigdy nie panikuje.

Zauważ, że pierwszym elementem iteratora jest nazwa samego programu (jest to konwencja we wszystkich głównych systemach operacyjnych), więc pierwszy argument jest w rzeczywistości drugim iterowanym elementem.

Prostym sposobem radzenia sobie z wynikiem argsjest przekonwertowanie go na Vec:

use std::env;

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

Możesz użyć całego standardowego zestawu narzędzi iteratora do pracy z tymi argumentami. Na przykład, aby pobrać tylko pierwszy argument:

use std::env;

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

Możesz znaleźć biblioteki na crates.io do analizowania argumentów wiersza poleceń:

  • docopt : po prostu piszesz wiadomość pomocy, a kod parsowania jest generowany dla Ciebie.
  • clap : opisujesz opcje, które chcesz przeanalizować za pomocą płynnego interfejsu API. Szybszy niż docopt i zapewnia większą kontrolę.
  • getopts : port popularnej biblioteki C. Niższy poziom i jeszcze większa kontrola.
  • structopt : zbudowany na klapie , jest jeszcze bardziej ergonomiczny w użyciu.
barjak
źródło
2
Również w przypadku rdzy 0.8 powinieneś użyć tylkoprintln(args[0])
Leo Correa
6
Powyższe komentarze (autorstwa @LeoCorrea / @ S4M) odnosiły się do starej wersji odpowiedzi; aktualna wersja odpowiedzi zawiera najbardziej aktualne informacje.
Nickolay
22

Docopt jest również dostępny dla Rusta, który generuje parser na podstawie ciągu znaków użycia. Jako bonus w Rust można użyć makra do automatycznego generowania struktury i dekodowania na podstawie typu:

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

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

I możesz uzyskać argumenty za pomocą:

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

README i dokumentacja zawierają wiele pełnych przykładów roboczych.

Zastrzeżenie: jestem jednym z autorów tej biblioteki.

BurntSushi5
źródło
10

Dla mnie getopts zawsze wydawały się zbyt niskie, a docopt.rs było zbyt magiczne. Chcę czegoś wyraźnego i prostego, co nadal zapewnia wszystkie funkcje, jeśli ich potrzebuję.

Tutaj przydaje się clap-rs .
To trochę jak argparse z Pythona. Oto przykład, jak to wygląda:

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();

Możesz uzyskać dostęp do swoich parametrów w następujący sposób:

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);

(Skopiowane z oficjalnej dokumentacji )

mre
źródło
1
Podoba mi się, że clap-rs pozwala zdefiniować interfejs w pliku yaml. Ponadto tworzy naprawdę ładnie wyglądające oświadczenia dotyczące użytkowania.
Chuck Wooters,
Pomogło mi to szybko skonfigurować aplikację CLI. Dziękuję Ci!
dimitarvp
4

Od wersji 0.8 / 0.9 poprawna ścieżka do funkcji args () będzie wyglądać następująco ::std::os::args:

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

Wygląda na to, że Rust jest nadal dość niestabilny, nawet przy standardowym IO, więc może się dość szybko zdezaktualizować.

Nacięcie
źródło
Dziękuję za aktualizację! Chyba będę musiał ponownie rozważyć zaakceptowaną odpowiedź po wydaniu 1.0.
shutefan
3

Rdza znowu się zmieniła. os::args()jest przestarzały na korzyść std::args(). Ale std::args()nie jest tablicą, zwraca iterator . Możesz iterować po argumentach wiersza poleceń, ale nie możesz uzyskać do nich dostępu za pomocą indeksów dolnych.

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

Jeśli chcesz, aby argumenty wiersza poleceń były wektorami ciągów, zadziała to teraz:

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

Rdza - naucz się przyjmować ból zmiany.

John Nagle
źródło
8
Teraz musisz tylko to zrobić env::args().collect().
tshepang
2

to, co powiedział @barjak, działa dla łańcuchów, ale jeśli potrzebujesz argumentu jako liczby (w tym przypadku uint), musisz przekonwertować w ten sposób:

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
źródło
2

Sprawdź również structopt:

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
źródło
1

Od nowszych wersji Rusta (Rust> 0,10 / 11) składnia tablicy nie będzie działać. Będziesz musiał użyć metody get.

[Edytuj] Składnia tablicy działa (ponownie) w nocy. Możesz więc wybrać między indeksem pobierającym lub indeksem tablicy.

use std::os;

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

// Compile
 rustc args.rs && ./args hello-world // returns hello-world
Stormpat
źródło
To jest przestarzałe stwierdzenie. Najnowsze nightlety Rust obsługują składnię indeksowania na Vecs. Myślę, że jest tam przez miesiąc. Zobacz ten przykład .
Vladimir Matveev
1

Rust ewoluował od czasu odpowiedzi Calvina z maja 2013 r. Teraz można przeanalizować argumenty wiersza poleceń za pomocą 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
źródło
Tak dla przypomnienia: as_slice()już nie istnieje i &argspowinno być używane zamiast tego.
Slava Semushin
1

Rozdział książki Rust „No stdlib” opisuje, jak uzyskać dostęp do parametrów wiersza poleceń (w inny sposób).

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

Teraz przykład ma również, #![no_std]co, jak sądzę, oznacza, że ​​normalnie biblioteka std miałaby prawdziwy punkt wejścia dla twojego pliku binarnego i wywołałaby funkcję globalną o nazwie main(). Inną opcją jest „wyłączenie mainpodkładki” za pomocą #![no_main]. Co, jeśli się nie mylę, oznacza powiedzenie kompilatorowi, że przejmujesz pełną kontrolę nad sposobem uruchamiania programu.

#![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
}

Nie sądzę, żeby to był „dobry” sposób robienia rzeczy, jeśli chcesz tylko czytać argumenty wiersza poleceń. std::osModuł wspomniano w innych odpowiedzi wydaje się być o wiele lepszy sposób robienia rzeczy. W celu uzupełnienia zamieszczam tę odpowiedź.

thecoshman
źródło