Принятие аргументов командной строки в консольном приложении

Создадим новый проект консольного приложения с помощью команды cargo new --bin minigrep для того, чтобы различать наше приложение от grep:

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

Первой задачей для реализации в нашем приложении будет принятие двух переменных командной строки: имени файла и строки для поиска. Мы будем запускать нашу программ у с помощью команды cargo run:

$ cargo run searchstring example-filename.txt

Пока наша программа не может обработать входные параметры командной строки. Для этой цели существует специальная библиотека в crates.io, которая может помощь решить данную задачу.

Чтение значений аргументов

Для того чтобы быть уверенным, что программа может получать значения аргументов командной строки, первое что нужно сделать, это подключить функционал стандартной библиотеки: std::env. Эта функция возвращает итератор (iterator) аргументов командной строки. Мы сейчас не будем вдаваться в подробности работы итераторов. Разберём эту тему в Главе 13. Сейчас нужно лишь понять, что итераторы создают последовательность значений и мы можем вызвать метод std::env::args().collect() для того, чтобы передать их в коллекцию. Например, в вектор.

Рассмотрим пример (12-1), в котором программа будет считывать значения командной строки и сохранять их в вектор.

Filename: src/main.rs

use std::env;

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

Listing 12-1: Считывание аргументов командной строки в вектор и печать этих данных на консоль

Разберем код этой программы. В первой строке мы добавляем импорт модуля std::env. После импорта модуля в коде программы становятся доступна функция args. Обратите внимания на модульную вложенность этой функции std::env::args. Для того чтобы сократить длину пути к вложенной функции мы заранее объявляем доступ к содержанию модуля командой use. Для предотвращения возможных недоразумений в принято, что функция модуля вызывается с префиксом родительского модуля env::args(). Это предотвращает возможные конфликты названий функций в текущем модуле и импортируемом модуле. Пожалуйста, разберите работу этого кода программы, посмотрите что возвращает функция arg(), каково содержание вектора. Запускайте программу с разными аргументами и смотрите как она работает.

Функция args и недействительный символы Юникода (Unicode)

Возможно вы не обратили внимания, но функция std::env::args не является универсальной. Если в качестве аргументов будут выступать недействильены символы юникода сработает макрос panic. Если же нужно принимать подобные аргументы, то необходимо использовать функция std::env::args_os. Эта функция возвращает std::ffi::OsString.

Метод args возвращает итератор. Вызов метода итератора collect возвращает вектор содержащий все переданные в командрую строку данные. Функция collect универсальная и может возвращать различные коллекции данных. Для того, чтобы для компиляции кода было достаточно информации, необходимо сообщить какой тип данных мы ожидаем в описание декларации переменной let args:.

Последняя строка нашего кода печатает содержимое вектора. Формат описания данные - отладка :?. Надеюсь, что дочитав до этой строки вы уже неоднократно запускали созданный код на выполнение:

$ cargo run
["target/debug/minigrep"]

$ cargo run needle haystack
...snip...
["target/debug/minigrep", "needle", "haystack"]

Вы, конечно же, обратили внимание на первый элемент в коллекции - это "target/debug/minigrep". Это, как вы поняли, имя бинарного файла. Работа обработки строк командной напоминает работу c-компилятора. Весьма удобно иметь доступ к имени программы, для написания сообщений или изменять поведение программы на основании вводимых данных. Для целей данной главы мы будем обращать внимание только на необходимые для решения поставленной задачи аргументы.

Сохранения значений аргументов в переменные

Вывода на печать значений аргументов командной строки - это простой тест возможности программы иметь доступа к аргументам командной строки. Далее, нам надо сохранить значение аргументов в переменные, чтобы иметь возможность их использования далее в программе. Пример реализации (Код 12-2):

Filename: src/main.rs

use std::env;

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

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

    println!("Searching for {}", query);
    println!("In file {}", filename);
}

Listing 12-2: Создание переменных для хранения шаблона поиска и имени файла

Как мы уже знаем из наших предыдущих упражнений, первый аргумент вектора хранит полное имя бинарного файла в ячейке вектора args[0]. Далее идут вводимые в командной строке аргументы. По условию задачи первым аргументом должен быть шаблон поискового запроса, а второй имя текстового файла, в котором будет осуществлён поиск.

Для проверки корректности работы нашей программы значения переменных были выведены на консоль. Далее запустим нашу программ со следующими аргументами: test и sample.txt:

$ cargo run test sample.txt
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/minigrep test sample.txt`
Searching for test
In file sample.txt

Отлично! Всё работает корректно. Для простоты изложения материала мы пока не будет вводить функционал отслеживающие неправильный ввод данных.