Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

Создадим новый проект, как обычно, с помощью 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 прочитать любые переданные ей аргументы командной строки, а затем собрать эти значения в вектор.

Filename: src/main.rs
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    dbg!(args);
}
Listing 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` 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.

Filename: 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}");
}
Listing 12-2: Создание переменных для хранения аргумента запроса и аргумента пути к файлу

Как мы увидели при печати вектора, имя программы занимает первое значение в векторе, 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

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