Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Accepter des arguments en ligne de commande

Créons un nouveau projet avec, comme toujours, cargo new. Nous appellerons notre projet minigrep pour le distinguer de l’outil grep que vous avez peut-être déjà sur votre système :

$ cargo new minigrep
     Created binary (application) `minigrep` project
$ cd minigrep

La première tâche consiste à faire en sorte que minigrep accepte ses deux arguments de ligne de commande : le chemin du fichier et une chaîne de caractères à rechercher. Autrement dit, nous voulons pouvoir exécuter notre programme avec cargo run, deux tirets pour indiquer que les arguments suivants sont destinés à notre programme plutôt qu’à cargo, une chaîne à rechercher, et un chemin vers un fichier dans lequel chercher, comme ceci :

$ cargo run -- searchstring example-filename.txt

Pour le moment, le programme généré par cargo new ne peut pas traiter les arguments que nous lui fournissons. Certaines bibliothèques existantes sur crates.io peuvent aider à écrire un programme qui accepte des arguments de ligne de commande, mais comme vous êtes en train d’apprendre ce concept, implémentons cette fonctionnalité nous-mêmes.

Lire les valeurs des arguments

Pour permettre à minigrep de lire les valeurs des arguments de ligne de commande que nous lui passons, nous aurons besoin de la fonction std::env::args fournie par la bibliothèque standard de Rust. Cette fonction renvoie un itérateur des arguments de ligne de commande passés à minigrep. Nous couvrirons les itérateurs en détail dans le [Chapitre 13][ch13]. Pour le moment, vous n’avez besoin de connaître que deux détails sur les itérateurs : les itérateurs produisent une série de valeurs, et nous pouvons appeler la méthode collect sur un itérateur pour le transformer en une collection, telle qu’un vecteur, qui contient tous les éléments produits par l’itérateur.

Le code de l’encart 12-1 permet à votre programme minigrep de lire tous les arguments de ligne de commande qui lui sont passés, puis de rassembler les valeurs dans un vecteur.

Filename: src/main.rs
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    dbg!(args);
}
Listing 12-1: Collecting the command line arguments into a vector and printing them

Tout d’abord, nous importons le module std::env dans la portée avec une instruction use afin de pouvoir utiliser sa fonction args. Remarquez que la fonction std::env::args est imbriquée dans deux niveaux de modules. Comme nous l’avons vu dans le [Chapitre 7][ch7-idiomatic-use], dans les cas où la fonction souhaitée est imbriquée dans plus d’un module, nous avons choisi d’importer le module parent dans la portée plutôt que la fonction elle-même. Ce faisant, nous pouvons facilement utiliser d’autres fonctions de std::env. C’est aussi moins ambigu que d’ajouter use std::env::args puis d’appeler la fonction avec simplement args, car args pourrait facilement être confondu avec une fonction définie dans le module courant.

La fonction args et l’Unicode invalide

Notez que std::env::args va paniquer si un argument contient de l’Unicode invalide. Si votre programme doit accepter des arguments contenant de l’Unicode invalide, utilisez plutôt std::env::args_os. Cette fonction renvoie un itérateur qui produit des valeurs OsString au lieu de valeurs String. Nous avons choisi d’utiliser std::env::args ici par souci de simplicité, car les valeurs OsString diffèrent selon la plateforme et sont plus complexes à manipuler que les valeurs String.

Sur la première ligne de main, nous appelons env::args, et nous utilisons immédiatement collect pour transformer l’itérateur en un vecteur contenant toutes les valeurs produites par l’itérateur. Nous pouvons utiliser la fonction collect pour créer de nombreux types de collections, nous annotons donc explicitement le type de args pour spécifier que nous voulons un vecteur de chaînes de caractères. Bien que vous ayez très rarement besoin d’annoter les types en Rust, collect est une fonction pour laquelle vous devez souvent le faire, car Rust n’est pas en mesure d’inférer le type de collection que vous souhaitez.

Enfin, nous affichons le vecteur en utilisant la macro de débogage. Essayons d’exécuter le code d’abord sans arguments, puis avec deux arguments : console {{#include ../listings/ch12-an-io-project/listing-12-01/output.txt}} console {{#include ../listings/ch12-an-io-project/output-only-01-with-args/output.txt}}

$ cargo run
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.61s
     Running `target/debug/minigrep`
[src/main.rs:5:5] args = [
    "target/debug/minigrep",
]
$ cargo run -- needle haystack
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.57s
     Running `target/debug/minigrep needle haystack`
[src/main.rs:5:5] args = [
    "target/debug/minigrep",
    "needle",
    "haystack",
]

Remarquez que la première valeur du vecteur est "target/debug/minigrep", qui est le nom de notre binaire. Cela correspond au comportement de la liste d’arguments en C, permettant aux programmes d’utiliser le nom par lequel ils ont été invoqués lors de leur exécution. Il est souvent pratique d’avoir accès au nom du programme au cas où vous voudriez l’afficher dans des messages ou modifier le comportement du programme en fonction de l’alias de ligne de commande utilisé pour l’invoquer. Mais pour les besoins de ce chapitre, nous l’ignorerons et ne conserverons que les deux arguments dont nous avons besoin.

Sauvegarder les valeurs des arguments dans des variables

Le programme est actuellement capable d’accéder aux valeurs spécifiées comme arguments de ligne de commande. Maintenant, nous devons sauvegarder les valeurs des deux arguments dans des variables afin de pouvoir utiliser ces valeurs dans le reste du programme. Nous faisons cela dans l’encart 12-2.

Filename: src/main.rs
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();

    let query = &args[1];
    let file_path = &args[2];

    println!("Searching for {query}");
    println!("In file {file_path}");
}
Listing 12-2: Creating variables to hold the query argument and file path argument

Comme nous l’avons vu lorsque nous avons affiché le vecteur, le nom du programme occupe la première valeur du vecteur à args[0], nous commençons donc les arguments à l’index 1. Le premier argument que prend minigrep est la chaîne que nous recherchons, nous plaçons donc une référence au premier argument dans la variable query. Le second argument sera le chemin du fichier, nous plaçons donc une référence au second argument dans la variable file_path.

Nous affichons temporairement les valeurs de ces variables pour prouver que le code fonctionne comme prévu. Exécutons à nouveau ce programme avec les arguments test et sample.txt : console {{#include ../listings/ch12-an-io-project/listing-12-02/output.txt}}

$ cargo run -- test sample.txt
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
     Running `target/debug/minigrep test sample.txt`
Searching for test
In file sample.txt

Parfait, le programme fonctionne ! Les valeurs des arguments dont nous avons besoin sont sauvegardées dans les bonnes variables. Plus tard, nous ajouterons de la gestion d’erreurs pour traiter certaines situations potentiellement erronées, comme lorsque l’utilisateur ne fournit aucun argument ; pour l’instant, nous ignorerons cette situation et travaillerons plutôt sur l’ajout de fonctionnalités de lecture de fichiers.