Принятие аргументов командной строки
Создадим новый проект консольного приложения как обычно с помощью команды cargo new
. Мы назовём проект minigrep
, чтобы различать наше приложение от grep
, которое возможно уже есть в вашей системе.
$ cargo new minigrep
Created binary (application) `minigrep` project
$ cd minigrep
Первая задача - заставить minigrep
принимать два аргумента командной строки: путь к файлу и строку для поиска. То есть мы хотим иметь возможность запускать нашу программу через cargo run
, с использованием двойного дефиса, чтобы указать, что следующие аргументы предназначены для нашей программы, а не для cargo
, строки для поиска и пути к файлу в котором нужно искать, как описано ниже:
$ cargo run -- searchstring example-filename.txt
В данный момент программа сгенерированная cargo new
не может обрабатывать аргументы, которые мы ей передаём. Некоторые существующие библиотеки на crates.io могут помочь с написанием программы, которая принимает аргументы командной строки, но так как вы просто изучаете эту концепцию, давайте реализуем эту возможность сами.
Чтение значений аргументов
Чтобы minigrep
мог воспринимать значения аргументов командной строки, которые мы ему передаём, нам понадобится функция std::env::args
, входящая в стандартную библиотеку Rust. Эта функция возвращает итератор аргументов командной строки, переданных в minigrep
. Мы подробно рассмотрим итераторы в главе 13. Пока вам достаточно знать две вещи об итераторах: итераторы генерируют серию значений, и мы можем вызвать метод collect
у итератора, чтобы создать из него коллекцию, например вектор, который будет содержать все элементы, произведённые итератором.
Код представленный в Листинге 12-1 позволяет вашей программе minigrep
читать любые переданные ей аргументы командной строки, а затем собирать значения в вектор.
Файл: src/main.rs
use std::env; fn main() { let args: Vec<String> = env::args().collect(); dbg!(args); }
Сначала мы вводим модуль std::env
в область видимости с помощью инструкции use
, чтобы мы могли использовать его функцию args
. Обратите внимание, что функция std::env::args
вложена в два уровня модулей. Как мы обсуждали в главе 7, в случаях, когда нужная функция оказывается вложенной в более чем один модуль, рекомендуется выносить в область видимости родительский модуль, а не функцию. Таким образом, мы можем легко использовать другие функции из std::env
. Это менее двусмысленно, чем добавление use std::env::args
и последующий вызов функции только с args
, потому что args
может быть легко принят за функцию, определённую в текущем модуле.
Функция
args
и недействительный Юникод символ (Unicode)Обратите внимание, что
std::env::args
вызовет панику, если какой-либо аргумент содержит недопустимый символ Юникода. Если вашей программе необходимо принимать аргументы, содержащие недопустимые символы 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` 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",
]
Обратите внимание, что первое значение в векторе "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 file_path = &args[2];
println!("Searching for {query}");
println!("In file {file_path}");
}
Как видно из распечатки вектора, имя программы занимает первое значение в векторе по адресу args[0]
, значит, аргументы начинаются с индекса 1
. Первый аргумент minigrep
- это строка, которую мы ищем, поэтому мы помещаем ссылку на первый аргумент в переменную query
. Вторым аргументом является путь к файлу, поэтому мы помещаем ссылку на второй аргумент в переменную file_path
.
Для проверки корректности работы нашей программы, значения переменных выводятся в консоль. Далее, запустим нашу программу со следующими аргументами: test
и sample.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
Отлично, программа работает! Нам нужно чтобы значения аргументов были сохранены в правильных переменных. Позже мы добавим обработку ошибок с некоторыми потенциальными ошибочными ситуациями, например, когда пользователь не предоставляет аргументы; сейчас мы проигнорируем эту ситуацию и поработаем над добавлением возможности чтения файла.