Прийняття аргументів командного рядка
Давайте створимо новий пакет за допомогою, як завжди, 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);
}
Спочатку ми вводимо модуль 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.
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
Чудово, програма працює! Значення аргументів, які нам потрібні, зберігаються у правильних змінних. Пізніше ми додамо обробку помилок, щоб впоратися з певними потенційно помилковими ситуаціями, наприклад, коли користувач не надає аргументів; наразі ми проігноруємо цю ситуацію і замість цього попрацюємо над додаванням можливостей читання файлів.