Récupérer les arguments de la ligne de commande

Créons un nouveau projet comme à l'accoutumée avec cargo new. Appelons notre projet minigrep pour le distinguer de l'outil grep que vous avez probablement déjà sur votre système.

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

La première tâche est de faire en sorte que minigrep utilise ses deux arguments en ligne de commande : le nom du fichier et la chaîne de caractères à rechercher. Autrement dit, nous voulons pouvoir exécuter notre programme avec cargo run, une chaîne de caractères à rechercher, et un chemin vers un fichier dans lequel chercher, comme ceci :

$ cargo run chaine_a_chercher fichier-exemple.txt

Pour l'instant, le programme généré par cargo new ne peut pas traiter les arguments que nous lui donnons. Certaines bibliothèques qui existent sur crates.io peuvent vous aider à écrire un programme qui prend des arguments en ligne de commande, mais comme vous apprenez juste ce concept, implémentons cette capacité par nous-mêmes.

Lire les valeurs des arguments

Pour permettre à minigrep de lire les valeurs des arguments de la ligne de commande que nous lui envoyons, nous allons avoir besoin d'une fonction fournie par la bibliothèque standard de Rust, qui est std::env::args. Cette fonction retourne un itérateur des arguments de la ligne de commande qui ont été donnés à minigrep. Nous verrons les itérateurs plus précisément au chapitre 13. Pour l'instant, vous avez juste à savoir deux choses à propos des itérateurs : les itérateurs engendrent une série de valeurs, et nous pouvons appeler la méthode collect sur un itérateur pour le transformer en collection, comme les vecteurs, qui contiennent tous les éléments qu'un itérateur engendrent.

Utilisez le code de l'encart 12-1 pour permettre à votre programme minigrep de lire tous les arguments qui lui sont envoyés et ensuite collecter les valeurs dans un vecteur.

Fichier : src/main.rs

use std::env;

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

Encart 12-1 : Collecter les arguments de la ligne de commande dans un vecteur et les afficher

D'abord, nous importons le module std::env dans la portée avec une instruction use afin que nous puissions utiliser sa fonction args. Notez que la fonction std::env::args est imbriquée sur deux niveaux de modules. Comme nous l'avons vu dans le chapitre 7, il est courant d'importer le module parent dans la portée plutôt que la fonction. En faisant ainsi, nous pouvons facilement utiliser les autres fonctions de std::env. C'est aussi moins ambigü que d'importer uniquement std::env::args et ensuite d'appeler la fonction avec seulement args, car args peu facilement être confondu avec une fonction qui est définie dans le module courant.

La fonction args et l'unicode invalide

Notez que std::env::args va paniquer si un des arguments contient de l'unicode invalide. Si votre programme a besoin d'utiliser des arguments qui contiennent de l'unicode invalide, utilisez plutôt std::env::args_os à la place. Cette fonction retourne un itérateur qui engendre des valeurs OsString plutôt que des valeurs String. Nous avons choisi d'utiliser ici std::env::args par simplicité, car les valeurs OsString diffèrent selon la plateforme et c'est plus complexe de travailler avec par rapport aux valeurs de type String.

Dans la première ligne du main, nous appelons env::args, et nous utilisons immédiatement collect pour retourner un itérateur dans un vecteur qui contient toutes les valeurs engendrées par l'itérateur. Nous pouvons utiliser la fonction collect pour créer n'importe quel genre de collection, donc nous avons annoté explicitement le type de args pour préciser que nous attendions un vecteur de chaînes de caractères. Bien que nous n'ayons que très rarement d'annoter les types en Rust, collect est une fonction que vous aurez souvent besoin d'annoter car Rust n'est pas capable de déduire le type de collection que vous attendez.

Enfin, nous affichons le vecteur en utilisant la chaîne de formatage :?. Essayons d'abord de lancer le code sans arguments, puis ensuite avec deux arguments :

$ cargo run
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished dev [unoptimized + debuginfo] target(s) in 0.61s
     Running `target/debug/minigrep`
["target/debug/minigrep"]
$ cargo run aiguille botte_de_foin
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished dev [unoptimized + debuginfo] target(s) in 1.57s
     Running `target/debug/minigrep aiguille botte_de_foin`
["target/debug/minigrep", "aiguille", "botte_de_foin"]

Remarquez que la première valeur dans le vecteur est "target/debug/minigrep", qui est le nom de notre binaire. Cela correspond au fonctionnement de la liste d'arguments en C, qui laissent les programmes utiliser le nom sous lequel ils ont été invoqués dans leur exécution. C'est parfois pratique pour avoir accès au nom du programme dans le cas où vous souhaitez l'afficher dans des messages, ou changer le comportement du programme en fonction de ce que l'alias de la ligne de commande utilise pour invoquer le programme. Mais pour les besoins de ce chapitre, nous allons l'ignorer et récupérer uniquement les deux arguments dont nous avons besoin.

Enregistrer les valeurs des arguments dans des variables

L'affichage des valeurs du vecteur des arguments nous a démontré que le programme peut avoir accès aux valeurs envoyées en arguments d'une ligne de commande. Maintenant, nous avons besoin d'enregistrer les valeurs des deux arguments dans des variables afin que nous puissions utiliser les valeurs pour le reste du programme. C'est que nous faisons dans l'encart 12-2.

Fichier : src/main.rs

use std::env;

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

    let recherche = &args[1];
    let nom_fichier = &args[2];

    println!("On recherche : {}", recherche);
    println!("Dans le fichier : {}", nom_fichier);
}

Encart 12-2 : Création de variables pour récupérer les arguments recherche et nom_fichier

Comme nous l'avons vu lorsque nous avons affiché le vecteur, le nom du programme prend la première valeur dans le vecteur, dans args[0], donc nous allons commencer à l'indice 1. Le premier argument que prend minigrep est la chaîne de caractères que nous recherchons, donc nous insérons la référence vers le premier argument dans la variable recherche. Le second argument sera le nom du fichier, donc nous insérons une référence vers le second argument dans la variable nom_fichier.

Nous affichons temporairement les valeurs de ces variables pour prouver que le code fonctionne bien comme nous le souhaitons. Lançons à nouveau ce programme avec les arguments test et example.txt :

$ cargo run test exemple.txt
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished dev [unoptimized + debuginfo] target(s) in 0.0s
     Running `target/debug/minigrep test exemple.txt`
On recherche : test
Dans le fichier : exemple.txt

Très bien, notre programme fonctionne ! Les valeurs des arguments dont nous avons besoin sont enregistrées dans les bonnes variables. Plus tard, nous allons ajouter de la gestion d'erreurs pour pallier aux potentielles situations d'erreurs, comme lorsque l'utilisateur ne fournit pas d'arguments ; pour le moment, nous allons ignorer ces situations et continuer à travailler pour l'ajout d'une capacité de lecture de fichier, à la place.