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

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

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

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

$ cargo run searchstring example-filename.txt

В данный момент программа сгенерированная cargo new не может обрабатывать аргументы, которые мы ей передаём. Некоторые существующие библиотеки на crates.io могут помочь с написанием программы, которая принимает аргументы командной строки, но так как вы просто изучаете эту концепцию, давайте реализуем эту возможность сами.

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

Чтобы сделать возможным в minigrep чтение передаваемых значений аргументов командной строки, нам понадобится функция, предоставляемая в стандартной библиотеке Rust std::env::args. Эта функция возвращает итератор аргументов командной строки, которые были переданы в minigrep . Мы рассмотрим итераторы полностью в Главе 13 , в данный момент вам нужно знать про итераторы только две детали: итераторы производят серию значений и мы можем вызвать collect метод на итераторе, чтобы превратить его в коллекцию, такую как вектор, содержащий все элементы, которые создаёт итератор.

Используйте код в листинге 12-1, чтобы позволить программе minigrep читать любые аргументы командной строки передающиеся ей, а затем собирать эти значения в вектор.

Файл: src/main.rs

use std::env;

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

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

Сначала подключаем модуль std::env в область видимости с помощью оператора use, чтобы иметь возможность использовать у неё функцию args. Обратите внимание, что функция std::env::args вложена на двух уровнях модулей. Как мы обсуждали в главе 7 в случаях, когда желаемая функция вложена в более чем один модуль, то обычно удобно подключить в область видимости родительский модуль, а не функцию. Таким образом, мы можем легко использовать другие функции из std::env. Это также менее двусмысленно, чем добавление use std::env::args, а затем вызов функции только с args, потому что args может быть легко принята за функцию, определённую в текущем модуле.

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

Обратите внимание, что std::env::args будет паниковать, если какой-либо переданный аргумент содержит недопустимый Unicode символ. Если ваша программа должна принимать аргументы, содержащие недопустимые Unicode символы, используйте вместо этого std::env::args_os. Эта функция возвращает итератор, который создаёт значения OsString вместо значений String. Мы выбрали использование std::env::args для простоты, потому что OsString значения отличаются на разных платформах и более сложны для обработки, чем значения String.

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

В конце мы печатаем вектор с использованием средства форматированной отладки :?. Давай попробуем выполнить код сначала без аргументов, а затем с двумя аргументами:

$ 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 needle haystack
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished dev [unoptimized + debuginfo] target(s) in 1.57s
     Running `target/debug/minigrep needle haystack`
["target/debug/minigrep", "needle", "haystack"]

Обратите внимание, что первое значение в векторе "target/debug/minigrep" является названием нашего двоичного файла. Это соответствует поведению списка аргументов в Си, позволяя программам использовать название из которой они были вызваны при выполнении. Часто бывает удобно иметь доступ к имени программы, если вы хотите распечатать его в сообщениях или изменить поведение программы в зависимости от того, какой псевдоним командной строки был использован для вызова программы. Но для целей этой главы, мы проигнорируем его и сохраним только два аргумента, которые нам нужны.

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

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

Файл: 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);
}

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

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

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

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

Отлично, программа работает! Нам нужно чтобы значения аргументов были сохранены в правильных переменных. Позже мы добавим обработку ошибок с некоторыми потенциальными ошибочными ситуациями, например, когда пользователь не предоставляет аргументы; сейчас мы проигнорируем эту ситуацию и поработаем над добавлением возможности чтения файла.