Comment accéder aux paramètres de ligne de commande?

153

Le didacticiel Rust n'explique pas comment prendre des paramètres à partir de la ligne de commande. fn main()s'affiche uniquement avec une liste de paramètres vide dans tous les exemples.

Quelle est la manière correcte d'accéder aux paramètres de ligne de commande à partir de main?

shutefan
la source

Réponses:

168

Vous pouvez accéder aux arguments de la ligne de commande à l'aide des fonctions std::env::argsou std::env::args_os. Les deux fonctions renvoient un itérateur sur les arguments. Le premier itère sur Strings (avec lesquels il est facile de travailler) mais panique si l'un des arguments n'est pas unicode valide. Ce dernier itère sur OsStrings et ne panique jamais.

Notez que le premier élément de l'itérateur est le nom du programme lui-même (c'est une convention dans tous les principaux systèmes d'exploitation), donc le premier argument est en fait le deuxième élément itéré.

Un moyen simple de gérer le résultat de argsest de le convertir en Vec:

use std::env;

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

Vous pouvez utiliser toute la boîte à outils de l'itérateur standard pour travailler avec ces arguments. Par exemple, pour récupérer uniquement le premier argument:

use std::env;

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

Vous pouvez trouver des bibliothèques sur crates.io pour analyser les arguments de ligne de commande:

  • docopt : il vous suffit d'écrire le message d'aide et le code d'analyse est généré pour vous.
  • clap : vous décrivez les options que vous souhaitez analyser à l'aide d'une API fluide. Plus rapide que docopt et vous donne plus de contrôle.
  • getopts : port de la bibliothèque C populaire. Niveau inférieur et encore plus de contrôle.
  • structopt : construit sur clap, il est encore plus ergonomique à utiliser.
barjak
la source
2
Aussi avec la rouille 0.8, vous devriez utiliser justeprintln(args[0])
Leo Correa
6
Les commentaires ci-dessus (par @LeoCorrea / @ S4M) faisaient référence à une ancienne version de la réponse; la version actuelle de la réponse contient les informations les plus à jour.
Nickolay
22

Docopt est également disponible pour Rust, qui génère un analyseur pour vous à partir d'une chaîne d'utilisation. En prime dans Rust, une macro peut être utilisée pour générer automatiquement la structure et effectuer un décodage basé sur le type:

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

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

Et vous pouvez obtenir les arguments avec:

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

Le README et la documentation contiennent de nombreux exemples de travail complets.

Avertissement: je suis l'un des auteurs de cette bibliothèque.

BurntSushi5
la source
10

Pour moi, getopts me semblait toujours trop bas et docopt.rs était trop magique. Je veux quelque chose d'explicite et de simple qui fournit toujours toutes les fonctionnalités si j'en ai besoin.

C'est là que clap-rs est utile.
Cela ressemble un peu à argparse de Python. Voici un exemple de son apparence:

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

Vous pouvez accéder à vos paramètres comme ceci:

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

(Copié de la documentation officielle )

mre
la source
1
J'aime que clap-rs vous permette de définir votre interface dans un fichier yaml. En outre, il produit de très belles déclarations d'utilisation.
Chuck Wooters
Cela m'a aidé à configurer rapidement mon application CLI. Je vous remercie!
dimitarvp
4

À partir de la version 0.8 / 0.9, le chemin correct vers la fonction args () serait ::std::os::args, c'est-à-dire:

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

Il semble que Rust soit encore assez volatile en ce moment même avec les E / S standard, donc cela peut devenir obsolète assez rapidement.

pseudo
la source
Merci pour la mise à jour! Je suppose que je devrai reconsidérer la réponse acceptée après la sortie de la version 1.0.
shutefan
3

La rouille a de nouveau changé. os::args()est obsolète au profit de std::args(). Mais ce std::args()n'est pas un tableau, il renvoie un itérateur . Vous pouvez parcourir les arguments de la ligne de commande, mais vous ne pouvez pas y accéder avec des indices.

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

Si vous voulez que les arguments de ligne de commande soient un vecteur de chaînes, cela fonctionnera maintenant:

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

Rust - apprenez à accepter la douleur du changement.

John Nagle
la source
8
Vous n'avez plus qu'à faire env::args().collect().
tshepang
2

ce que @barjak a dit fonctionne pour les chaînes, mais si vous avez besoin de l'argument sous forme de nombre (dans ce cas, un uint), vous devez convertir comme ceci:

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
la source
2

Consultez également 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
la source
1

À partir des nouvelles versions de Rust (Rust> 0.10 / 11), la syntaxe du tableau ne fonctionnera pas. Vous devrez utiliser la méthode get.

[Modifier] La syntaxe du tableau fonctionne (à nouveau) dans la nuit. Vous pouvez donc choisir entre l'index getter ou le tableau.

use std::os;

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

// Compile
 rustc args.rs && ./args hello-world // returns hello-world
Stormpat
la source
C'est une déclaration obsolète. Les derniers nightlies Rust prennent en charge la syntaxe d'indexation sur Vecs. Je suppose que c'est là depuis un mois environ. Voyez cet exemple .
Vladimir Matveev
1

Rust a évolué depuis la réponse de Calvin de mai 2013. Maintenant, on analyserait les arguments de ligne de commande avec 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
la source
Juste pour mémoire: as_slice()n'existe plus et &argsdevrait être utilisé à la place.
Slava Semushin
1

Le chapitre "No stdlib" du livre Rust explique comment accéder aux paramètres des lignes de commande (d'une autre manière).

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

Maintenant, l'exemple a également #![no_std]ce qui, je pense, signifie que normalement, la bibliothèque std aurait le vrai point d'entrée pour votre binaire et appellerait une fonction globale appelée main(). Une autre option consiste à «désactiver le mainshim» avec #![no_main]. Ce qui, si je ne me trompe pas, c'est dire au compilateur que vous prenez le contrôle total du démarrage de votre programme.

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

Je ne pense pas que ce soit une «bonne» façon de faire les choses si tout ce que vous voulez faire est de lire les arguments de la ligne de commande. Le std::osmodule mentionné dans d'autres réponses semble être une bien meilleure façon de faire les choses. Je poste cette réponse par souci de finition.

thecoshman
la source