Создайте свой первый инструмент командной строки в Rust

задняя часть Командная строка Программа перевода самородков Rust C++

В прекрасном мире программирования вы, возможно, слышали об этом новом языке под названием Rust. Это язык программирования системного уровня с открытым исходным кодом. Основное внимание уделяется производительности, безопасности памяти и параллелизму. На нем можно писать низкоуровневые приложения, такие как C/C++.

ты уже можешь бытьWeb AssemblyУвидел на сайте. Rust умеет компилировать приложения WASM, вы можетеWeb Assembly FAQНайдите много примеров на . Он также считаетсяservoКраеугольным камнем Servo является высокопроизводительный движок браузера, реализованный в Firefox.

Это может вас оттолкнуть, но мы не об этом. Мы покажем вам, как использовать его для создания инструментов командной строки, и вы можете найти в нем много интересного.

Почему ржавчина?

Ну, позвольте мне прояснить ситуацию. Я мог бы сделать инструменты командной строки на любом другом языке или фреймворке. Я могу выбрать C, Go, Ruby и т. д. Даже я могу использовать классический bash.

В середине 2018 года мне захотелось узнать что-то новое, Rust разжег мое любопытство, а также мне нужно было создать несколько простых гаджетов для автоматизации некоторых процессов в работе и личных проектах.

Установить

ты можешь использоватьRustupдля настройки вашей среды разработки, это основная точка входа для установки и настройки всех инструментов Rust на вашем компьютере.

Если вы работаете в Linux и MacOS, установку можно выполнить с помощью следующей команды:

$ curl <https://sh.rustup.rs> -sSf | sh

Если вы используете систему Windows, опять же, вам необходимоСайт Rustupскачать одинexeи беги.

Если вы используете Windows 10, я рекомендую вам использоватьWSLдля завершения установки. Это все, что нужно для установки, и теперь мы готовы создать наше первое приложение на Rust!

Ваше первое приложение на Rust

То, что мы собираемся сделать здесь, это по образцуcatдля создания UNIX-утилиты или, по крайней мере, упрощенной версии, назовем ееkt. Это приложение примет путь к файлу в качестве входных данных и отобразит содержимое файла на стандартном выходе терминала.

Чтобы создать базовый скелет этого приложения, мы будем использоватьCargoИнструмент. Это менеджер пакетов Rust, думайте о нем как о NPM (для разработчиков Javascript) или Bundler (для разработчиков Ruby) для инструментов Rust.

Откройте терминал, перейдите по пути, по которому вы хотите сохранить исходный код, и введите следующий код.

$ cargo init kt

Это создаст файл с именемktкаталог, в котором уже есть базовая структура нашего приложения.

если мыcdв этот каталог, мы увидим эту структуру каталогов. И, что удобно, в этом проекте по умолчанию уже инициализирован git. Отлично!

$ cd kt/
  |
  .git/
  |
  .gitignore
  |
  Cargo.toml
  |
  src/

Cargo.tomlФайл содержит основную информацию и информацию о зависимостях нашего приложения. Точно так же вы можете думать об этом как о приложении.package.jsonилиGemfileдокумент.

src/Каталог содержит исходные файлы для приложения, мы видим, что там только одинmain.rsдокумент. Проверив содержимое файла, мы видим, что есть только одинmainфункция.

fn main() {
    println!("Hello, world!");
}

Попробуйте создать этот проект. Поскольку внешних зависимостей нет, сборка должна происходить очень быстро.

$ cargo build
Compiling kt v0.1.0 (/Users/jeremie/Development/kitty)
Finished dev [unoptimized + debuginfo] target(s) in 2.82s

В режиме разработки вы можете сделать это, вызвавcargo runвыполнить двоичный файл (сcargo run --- my_argдля передачи аргументов командной строки).

$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.07s
Running `target/debug/kt`
Hello, world!

Поздравляем, у вас есть первое приложение на Rust, и вы только что выполнили шаги! 🎉

Разобрать первый аргумент командной строки

Как я уже говорил ранее в статье, мы пытаемся построить упрощенную версиюcatЗаказ. Наша цель — смоделироватьcatповедение, бегаkt myfile.txtПосле команды выведите содержимое файла в терминал.

Мы могли бы сами справиться с процессом анализа параметров, но, к счастью, есть инструмент Rust, который может помочь нам упростить этот процесс, а именно:Clap.

Это высокопроизводительный анализатор аргументов командной строки, который упрощает управление аргументами командной строки.

Первым шагом в использовании этого инструмента является открытиеCargo.tomlфайл и добавьте в него указанные зависимости. если вы никогда не имели дело с.tomlфайл не имеет значения, он такой же, как.INIФайлы очень похожи. Этот формат файла распространен в Rust.

В этом файле вы увидите, что некоторая информация уже заполнена, например, автор, версия и т. д. нам просто нужно[dependencies]Просто добавьте зависимости ниже.

[dependencies]
clap = "~2.32"

После сохранения файла нам нужно пересобрать проект, чтобы иметь возможность использовать библиотеку зависимостей. Несмотря на тоcargoскачать кромеclapНе беспокойтесь о файлах, отличных отclapТакже имеет необходимые зависимости.

$ cargo build
 Updating crates.io index
  Downloaded clap v2.32.0
  Downloaded atty v0.2.11
  Downloaded bitflags v1.0.4
  Downloaded ansi_term v0.11.0
  Downloaded vec_map v0.8.1
  Downloaded textwrap v0.10.0
  Downloaded libc v0.2.48
  Downloaded unicode-width v0.1.5
  Downloaded strsim v0.7.0
   Compiling libc v0.2.48
   Compiling unicode-width v0.1.5
   Compiling strsim v0.7.0
   Compiling bitflags v1.0.4
   Compiling ansi_term v0.11.0
   Compiling vec_map v0.8.1
   Compiling textwrap v0.10.0
   Compiling atty v0.2.11
   Compiling clap v2.32.0
   Compiling kt v0.1.0 (/home/jeremie/Development/kt)
    Finished dev [unoptimized + debuginfo] target(s) in 33.92s

Вышеупомянутое — это то, что нужно настроить, а затем мы можем начать и написать некоторый код для чтения нашего первого параметра командной строки.

Открытымmain.rsдокумент. Мы должны явно заявить, что хотим использовать библиотеку Clap.

extern crate clap;

use clap::{Arg, App};

fn main() {}

extern crateКлючевое слово используется для импорта зависимых библиотек, вам просто нужно добавить его в основной файл, и любой исходный файл вашего приложения может ссылаться на него.useраздел относится к тому, что вы будете использовать в этом файлеclapкакой модуль.

Краткое описание модулей Rust:

В Rust есть модульная система, позволяющая организованно повторно использовать код. Модуль — это пространство имен, содержащее определения функций или типов, и вы можете выбрать, будут ли эти определения видны вне его модуля (общедоступного или частного). - Документация по ржавчине

Здесь мы заявляем, что хотим использоватьArgиAppмодуль. Мы хотим, чтобы наше приложение имелоFILEпараметр, он будет содержать путь к файлу. Clap может помочь нам быстро реализовать эту функцию. Здесь есть способ цепочки вызовов методов, что очень приятно.

fn main() {
    let matches = App::new("kt")
      .version("0.1.0")
      .author("Jérémie Veillet. jeremie@example.com")
      .about("A drop in cat replacement written in Rust")
      .arg(Arg::with_name("FILE")
            .help("File to print.")
            .empty_values(false)
        )
      .get_matches();
}

Скомпилируйте и выполните снова, за исключением переменнойmatchesскомпилировать предупреждения (для разработчиков Ruby вы можете добавить переменную_, который говорит компилятору, что переменная является необязательной), она не должна выводить многое другое.

Если вы пройдете приложение-hили-Vпараметры, программа автоматически создаст справочное сообщение и информацию о версии. Я не знаю, что вы думаете об этом, но я думаю, что это 🔥🔥🔥.

$ cargo run -- -h
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `target/debug/kt -h`
kt 0.1.0
Jérémie Veillet. jeremie@example.com
A drop-in cat replacement written in Rust

 USAGE:
    kt [FILE]

 FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

 ARGS:
    <FILE>    File to print.

$ cargo run --- -V
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running target/debug/kt -V
kt 0.1.0

Мы также можем попробовать запустить программу без каких-либо аргументов и посмотреть, что произойдет.

$ cargo run --
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
  Running `target/debug/kt`

Ничего не случилось. Это поведение по умолчанию, которое должно происходить каждый раз, когда создается инструмент командной строки. Я не думаю, что какое-либо действие должно запускаться без передачи каких-либо параметров приложению. Хотя иногда это и не так, в большинстве случаев никогда не делайте того, что пользователь никогда не собирался делать.

Теперь, когда у нас есть параметры, мы можем копаться в том, какзахватыватьЭтот аргумент командной строки и печатает что-то на стандартный вывод.

Для этого мы можем использоватьclapсерединаvalue_ofметод. Пожалуйста, обратитесь кДокументацияпонять, как работает этот метод.

fn main() {
    let matches = App::new("kt")
      .version("0.1.0")
      .author("Jérémie Veillet. jeremie@example.com")
      .about("A drop in cat replacement written in Rust")
      .arg(Arg::with_name("FILE")
            .help("File to print.")
            .empty_values(false)
      )
      .get_matches();

     if let Some(file) = matches.value_of("FILE") {
        println!("Value for file argument: {}", file);
    }
}

На этом этапе вы можете запустить приложение и передать в качестве параметра случайную строку, которая будет отображаться в вашей консоли.

$ cargo run -- test.txt
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
  Running `target/debug/kt test.txt`
Value for file argument: test.txt

Обратите внимание, что в настоящее время мы фактически не проверяем существование этого файла. Итак, как мы должны этого достичь?

Существует стандартная библиотека, которая позволяет нам проверить, существует ли файл или каталог, и она очень проста в использовании. этоstd::pathбиблиотека. оно имеетexistsметод, который может помочь нам проверить, существует ли файл.

Как упоминалось ранее, используйтеuseключевое слово, чтобы добавить зависимые библиотеки, а затем напишите следующий код. Вы можете видеть, что мы используемIf-ElseУсловный элемент управления печатает некоторый текст на выходе.println!метод пишет в стандартный выводstdouteprintln!будет писать в стандартный вывод ошибокstderr.

extern crate clap;

use clap::{Arg, App};
use std::path::Path;
use std::process;

 fn main() {
    let matches = App::new("kt")
      .version("0.1.0")
      .author("Jérémie Veillet. jeremie@example.com")
      .about("A drop in cat replacement written in Rust")
      .arg(Arg::with_name("FILE")
            .help("File to print.")
            .empty_values(false)
        )
      .get_matches();

     if let Some(file) = matches.value_of("FILE") {
        println!("Value for file argument: {}", file);
        if Path::new(&file).exists() {
            println!("File exist!!");
        }
        else {
            eprintln!("[kt Error] No such file or directory.");
            process::exit(1); // 程序错误终止时的标准退出码
        }
    }
}

Мы почти закончили! Теперь нам нужно прочитать содержимое файла и отобразить результат вstdoutсередина.

Точно так же мы будем использоватьFileСтандартная библиотека для чтения файлов. мы будем использоватьopenметод, чтобы прочитать содержимое файла, а затем записать его в строковый объект, который будетstdoutотображается в.

extern crate clap;

use clap::{Arg, App};
use std::path::Path;
use std::process;
use std::fs::File;
use std::io::{Read};

fn main() {
    let matches = App::new("kt")
      .version("0.1.0")
      .author("Jérémie Veillet. jeremie@example.com")
      .about("A drop in cat replacement written in Rust")
      .arg(Arg::with_name("FILE")
            .help("File to print.")
            .empty_values(false)
        )
      .get_matches();
    if let Some(file) = matches.value_of("FILE") {
        if Path::new(&file).exists() {
           println!("File exist!!");
           let mut f = File::open(file).expect("[kt Error] File not found.");
           let mut data = String::new();
           f.read_to_string(&mut data).expect("[kt Error] Unable to read the  file.");
           println!("{}", data);
        }
        else {
            eprintln!("[kt Error] No such file or directory.");
            process::exit(1);
        }
    }
}

Соберите и снова запустите этот код. Поздравляем! Теперь у нас есть полнофункциональный инструмент! 🍾

$ cargo build
   Compiling kt v0.1.0 (/home/jeremie/Development/kt)
    Finished dev [unoptimized + debuginfo] target(s) in 0.70s
$ cargo run -- ./src/main.rs
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `target/debug/kt ./src/main.rs`
File exist!!
extern crate clap;

use clap::{Arg, App};
use std::path::Path;
use std::process;
use std::fs::File;
use std::io::{Read};

 fn main() {
    let matches = App::new("kt")
      .version("0.1.0")
      .author("Jérémie Veillet. jeremie@example.com")
      .about("A drop in cat replacement written in Rust")
      .arg(Arg::with_name("FILE")
            .help("File to print.")
            .empty_values(false)
        )
      .get_matches();

     if let Some(file) = matches.value_of("FILE") {
        if Path::new(&file).exists() {
            println!("File exist!!");
            let mut f = File::open(file).expect("[kt Error] File not found.");
            let mut data = String::new();
            f.read_to_string(&mut data).expect("[kt Error] Unable to read the  file.");
            println!("{}", data);
        }
        else {
            eprintln!("[kt Error] No such file or directory.");
            process::exit(1);
        }
    }
}

немного улучшить

Теперь наше приложение может получать параметр иstdoutрезультаты отображаются в.

Мы можем немного настроить производительность всей фазы печати, используяwriteln!заменитьprintln!. это вУчебник по выводу RustЕсть хорошее объяснение. Попутно мы можем подчистить код, удалить ненужную печать и настроить возможные сценарии ошибок.

extern crate clap;

use clap::{Arg, App};
use std::path::Path;
use std::process;
use std::fs::File;
use std::io::{Read, Write};

fn main() {
    let matches = App::new("kt")
      .version("0.1.0")
      .author("Jérémie Veillet. jeremie@example.com")
      .about("A drop in cat replacement written in Rust")
      .arg(Arg::with_name("FILE")
            .help("File to print.")
            .empty_values(false)
        )
      .get_matches();

     if let Some(file) = matches.value_of("FILE") {
        if Path::new(&file).exists() {
            match File::open(file) {
                Ok(mut f) => {
                    let mut data = String::new();
                    f.read_to_string(&mut data).expect("[kt Error] Unable to read the  file.");
                    let stdout = std::io::stdout(); // 获取全局 stdout 对象
                    let mut handle = std::io::BufWriter::new(stdout); // 可选项:将 handle 包装在缓冲区中
                    match writeln!(handle, "{}", data) {
                        Ok(_res) => {},
                        Err(err) => {
                            eprintln!("[kt Error] Unable to display the file contents. {:?}", err);
                            process::exit(1);
                        },
                    }
                }
                Err(err) => {
                    eprintln!("[kt Error] Unable to read the file. {:?}", err);
                    process::exit(1);
                },
            }
        }
        else {
            eprintln!("[kt Error] No such file or directory.");
            process::exit(1);
        }
    }
}
$ cargo run -- ./src/main.rs
  Finished dev [unoptimized + debuginfo] target(s) in 0.02s
    Running `target/debug/kt ./src/main.rs`
extern crate clap;

use clap::{Arg, App};
use std::path::Path;
use std::process;
use std::fs::File;
use std::io::{Read, Write};

 fn main() {
    let matches = App::new("kt")
      .version("0.1.0")
      .author("Jérémie Veillet. jeremie@example.com")
      .about("A drop in cat replacement written in Rust")
      .arg(Arg::with_name("FILE")
            .help("File to print.")
            .empty_values(false)
        )
      .get_matches();

     if let Some(file) = matches.value_of("FILE") {
        if Path::new(&file).exists() {
            match File::open(file) {
                Ok(mut f) => {
                    let mut data = String::new();
                    f.read_to_string(&mut data).expect("[kt Error] Unable to read the  file.");
                    let stdout = std::io::stdout(); // 获取全局 stdout 对象
                    let mut handle = std::io::BufWriter::new(stdout); // 可选项:将 handle 包装在缓冲区中
                    match writeln!(handle, "{}", data) {
                        Ok(_res) => {},
                        Err(err) => {
                            eprintln!("[kt Error] Unable to display the file contents. {:?}", err);
                            process::exit(1);
                        },
                    }
                }
                Err(err) => {
                    eprintln!("[kt Error] Unable to read the file. {:?}", err);
                    process::exit(1);
                },
            }
        }
        else {
            eprintln!("[kt Error] No such file or directory.");
            process::exit(1);
        }
    }
}

Мы сделали! Мы закончили нашу упрощенную версию всего за 45 строк кода.catкоманда 🤡, и она работает очень хорошо!

Создавайте автономные приложения

Так что же нужно, чтобы собрать это приложение и установить его в файловую систему? Попроси груз о помощи!

cargo buildпринять один---releaseфлаги, чтобы мы могли указать окончательную версию исполняемого файла, который нам нужен.

$ cargo build --release
   Compiling libc v0.2.48
   Compiling unicode-width v0.1.5
   Compiling ansi_term v0.11.0
   Compiling bitflags v1.0.4
   Compiling vec_map v0.8.1
   Compiling strsim v0.7.0
   Compiling textwrap v0.10.0
   Compiling atty v0.2.11
   Compiling clap v2.32.0
   Compiling kt v0.1.0 (/home/jeremie/Development/kt)
    Finished release [optimized] target(s) in 28.17s

Полученный исполняемый файл находится в этом подкаталоге:./target/release/kt.

Вы можете скопировать этот файл на свойPATHпеременные среды или используйте команду cargo для его автоматической установки. Приложение будет установлено в~/.cargo/bin/каталог (убедитесь, что каталог находится в~/.bashrcили~/.zshrcизPATHпеременные окружения).

$ cargo install --path .
  Installing kt v0.1.0 (/home/jeremie/Development/kt)
    Finished release [optimized] target(s) in 0.03s
  Installing /home/jeremie/.cargo/bin/kt

Теперь мы можем использовать прямо в терминалеktКоманда вызывает наше приложение! \о/

$ kt -V
kt 0.1.0

Суммировать

Мы создали гаджет командной строки всего из нескольких строк кода на Rust, который принимает путь к файлу в качестве входных данных иstdoutдля отображения содержимого файла.

вы можете найти это в этомРепозиторий GitHubНайдите весь исходный код в этой статье.

Ваша очередь улучшить этот инструмент!

  • Вы можете добавить параметр командной строки, чтобы контролировать, добавляются ли номера строк к выходным данным (-nвариант).
  • чтобы отобразить только часть файла, то нажатием на клавиатуреENTERклавишу для отображения остальных.
  • использоватьkt myfile.txt myfile2.txt myfile3.txtТакой синтаксис открывает сразу несколько файлов.

Не стесняйтесь сказать мне, что вы сделали с ним! 😎

Особая благодарность Анаис, которая помогла отредактировать эту статью.👍

Узнайте больше

  • cat: страница Википедии для утилиты cat.
  • kt-rs
  • Rust Cookbook
  • Clap: полнофункциональный высокопроизводительный анализатор аргументов командной строки Rust.
  • Reqwest: простой, но мощный HTTP-клиент для Rust.
  • Serde: Фреймворк сериализации для Rust.
  • crates.io: сайт регистрации инструментов для сообщества Rust.

Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.


Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из ИнтернетаНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,продукт,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.