Аргументы командной строки
Как обычно, создадим новый проект командой 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 прочитать все переданные
ей аргументы командной строки и собрать их в вектор.
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
dbg!(args);
}
Сначала инструкцией use подключаем модуль std::env, чтобы пользоваться его
функцией args. Заметьте, что функция std::env::args вложена в два уровня
модулей. Как мы обсуждали в главе 7, если
нужная функция вложена больше чем в один модуль, мы подключаем родительский
модуль, а не саму функцию. Так можно легко пользоваться и другими функциями
из std::env. К тому же это однозначнее, чем написать use std::env::args и
вызывать функцию просто как args: args легко принять за функцию,
определённую в текущем модуле.
Функция args и некорректный Юникод
Учтите, что std::env::args запаникует, если какой-то аргумент содержит
некорректный Юникод. Если программе нужно принимать такие аргументы,
используйте 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", имя
нашего бинарника. Так же устроен список аргументов в C: программа может
узнать, под каким именем её запустили. Это часто удобно: например, чтобы
выводить имя программы в сообщениях или менять поведение программы в
зависимости от того, через какой псевдоним командной строки её вызвали. Но в
этой главе оно нам не нужно, так что мы его пропустим и сохраним только два
нужных аргумента.
Сохраняем значения аргументов в переменные
Программа уже умеет получать значения, переданные как аргументы командной строки. Теперь нужно сохранить значения двух аргументов в переменные, чтобы пользоваться ими в остальной программе. Это сделано в листинге 12-2.
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
Отлично, программа работает! Значения нужных аргументов сохраняются в правильные переменные. Позже мы добавим обработку ошибок для некоторых нештатных ситуаций — например, когда пользователь не передал ни одного аргумента. А пока не будем об этом думать и займёмся чтением файла.