Pipeviewer: небольшая «pv» перезапись

Если вам когда-либо приходилось передавать большой объем данных из какой-либо программы foo в другую программу bar, вы, наверное, знакомы с просмотрщик труб заявление. Если вы не знакомы с приложением, вот небольшая демонстрация минимальной перезаписи:

демонстрация поведения ПВ

По сути, pv примет любой вклад от stdin (или файлы) и отправьте его stdout показывая прогресс по stderr. Обратите внимание, что оригинал pv имеет еще несколько функций, которые не были целями этого проекта.

Cargo.toml (зависимости)

[dependencies]
anyhow = "1.0"
indicatif = "0.15.0"
structopt = "0.3"

main.rs

use anyhow::Result;
use indicatif::{ProgressBar, ProgressStyle};
use std::fs::File;
use std::io::{self, ErrorKind, Read};
use std::path::PathBuf;
use structopt::StructOpt;

#[derive(Debug, StructOpt)]
#[structopt(name = "pipeviewer", about = "A pipe inspecting application.")]
struct Opt {
    /// Input file, stdin if not specified
    #[structopt(parse(from_os_str))]
    input: Option<PathBuf>,
}

fn main() -> Result<()> {
    let opts = Opt::from_args();

    let (mut input, len): (Box<dyn Read>, Option<u64>) = if let Some(file) = opts.input {
        let file = File::open(file)?;
        let len = file.metadata()?.len();
        (Box::new(file), Some(len))
    } else {
        (Box::new(io::stdin()), None)
    };

    let pb = if let Some(len) = len {
        let pb = ProgressBar::new(len);
        pb.set_style(ProgressStyle::default_bar().template(
            "[{elapsed_precise}] {bar} {bytes_per_sec} [{bytes}/{total_bytes}] ETA: {eta}",
        ));
        pb
    } else {
        let pb = ProgressBar::new_spinner();
        pb.set_style(
            ProgressStyle::default_spinner()
                .template("[{elapsed_precise}] {spinner} {bytes_per_sec} [{bytes}]"),
        );
        pb
    };
    let mut output = pb.wrap_write(io::stdout());

    match io::copy(&mut input, &mut output) {
        Ok(_) => Ok(()),
        Err(e) if e.kind() == ErrorKind::BrokenPipe => Ok(()),
        Err(e) => Err(e.into()),
    }
}

К сожалению, в коде немного преобладает стиль индикатора выполнения. Однако я не хотел показывать бессмысленную полосу выполнения для неизвестного количества данных (например, foo | pv | bar), и я не хотел показывать никакого прогресса если объем данных известен (например, pv somefile).

Меня больше всего интересуют вопросы стиля, хитрости и подобные грехи Rust. Я знаю, что могу добавить некоторые дополнительные функции (например, больше файлов в качестве аргументов, подсказки размера для STDIN, поддержку нескольких pvв одной трубе) и с радостью черпаю вдохновение из обзоров, но в первую очередь я хочу улучшить свои навыки работы с Rust :).

Некоторые дополнительные замечания

Этот раздел дает лишь некоторую предысторию и его совершенно необязательно читать :).

Мотивация

Это переписывание вдохновлено Удеми конечно Практическое системное программирование на Rust. Однако я сильно отклоняюсь от первоначального курса, поскольку он вводит многопоточность (через crossbeam) и другие (слегка) лишние функции.

Цели lazy pipeviewer rewrite

  • по возможности используйте уже существующие ящики (не наезжай Национальные институты здравоохранения США)
  • взять либо одно имя файла, либо использовать stdin в качестве ввода
  • промыть все в стандартный вывод (но не обращать внимания на сломанные трубы)
  • не переусердствуйте, например
    • нет многопоточности, если одноядерная производительность достаточно высока
    • нет BufReader или же BufWriter если небуферизованные варианты кажутся достаточно быстрыми
    • нет явной буферизации через Read::read(&mut [u8]) если есть возможность, ПОЦЕЛУЙ!

Приглушить это

При написании этого ленивого кода был момент, когда while let Ok(n) = input.read(&mut buf) был введен, но с учетом этого io::copy показалось достаточно быстрым, я не стал использовать его в финальной версии. То же самое касается BufReader и BufWriter, так как они не улучшили поведение на моих машинах. Могут потребоваться дополнительные тесты, но это может более или менее победить ленивую часть;).

Знакомство с ящиками (для других проектов)

Весь этот проект более или менее предназначен как упражнение для а) поиска подходящих ящиков и б) их правильного использования. Разбор аргумента? Почти всегда необходимо. Правильная обработка ошибок? Обязательным. Показываете красивый индикатор выполнения на пути к завершению? Большой бонус!

Зависимости

Что касается зависимостей, я выбрал

  • structopt для анализа единственного аргумента (изначально у меня было больше аргументов)
  • anyhow для предоставления сообщений об ошибках
  • indicatif для индикатора выполнения

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *