LINUX.ORG.RU

Раст, итераторы, командная строка

 


0

4

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

По идее, для получения имени программы правильно будет использовать env::current_exe. Но получается не очень, да ещё и ошибки не обрабатываются:

println!("{}", std::env::current_exe().unwrap().display());

Если обработать, то получается вот так:

println!("{}", std::env::current_exe().ok().map_or("test".to_string(), |path| path.display().to_string()));

Поэтому хотел не заморачивать и просто использовать первый аргумент. Получилось вот так:

let args = std::env::args();
if args.len() < 2 {
    println!("{}", args.take(1).last().unwrap_or("test".to_string()));
}

let text = args.skip(1).fold(String::new(), |result, param| result + " " + &param);

Собственно, вопросы:

1. Можно ли покрасивее получить имя программы?

2. Как поизящнее убрать первый пробел из получаемой строки?

3. Почему env::args возвращает итератор, а не вектор, как раньше?

★★★★★
Ответ на: комментарий от ozkriff

https://github.com/kbknapp/clap-rs или https://github.com/docopt/docopt.rs ?

Эх, хотел ведь дописать «библиотеки не предлагать» - задача ведь простая, именованных аргументов нет. Честно говоря, лень искать решают ли они вообще задачу «все параметры, кроме первого, в виде строки». Но получить имя программы обе библиотеки никак не помогают - там предполагается задавать это самому.

Да и меня больше интересовали нюансы работы с итераторами. Может я чего-то не понимаю с итераторами и есть более удобные способа получить первый элемент, чем take(1).last(). Конечно, есть next, но он требует мутабельной последовательности.

DarkEld3r ★★★★★
() автор топика

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

По идее, для получения имени программы правильно будет использовать env::current_exe.

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

Я бы так написал:

fn main() {
    use std::env;

    let args: Vec<String> = env::args().collect();
    let program_name = &args[0];

    if args.len() < 2 {
        println!("{}: invalid usage", program_name);
        return;
    }

    let concatenated_args = &args[1 ..].connect(" ");
}

2. Как поизящнее убрать первый пробел из получаемой строки?

trim

Legioner ★★★★★
()
Ответ на: комментарий от anonymous

Как там у вас в русте итераторы поживают?

В смысле?

DarkEld3r ★★★★★
() автор топика
Ответ на: комментарий от Legioner

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

Логично.

Я бы так написал:

Хотел избежать создания «ненужного» вектора.

trim

Про трим, конечно, знаю, но он возвращает ссылку на результат, не догадался сразу, что можно сделать копию.

DarkEld3r ★★★★★
() автор топика
Ответ на: комментарий от Legioner

Я на Rust не писал, но там же есть pattern matching? Почему бы не сделать что-то вроде (псевдокод)

match args {
  head:[] -> { println("invalid" }
  head:tail -> { let conc_args = tail.connect(" "); ... }
  _ -> { println("invalid" }
}

anonymous
()
Ответ на: комментарий от anonymous

Вот нашел синтаксис, но не нашел нормальной документации по расту, поэтому не уверен, что работает с итераторами. Кто Rust знает - поправит.

match args {
  [prog_name] => {
    println!("{}: invalid usage", prog_name);
  }
  [prog_name, .. other_args] => {
    let conc_args = other_args.connect();
    ...
  }
  _ => { }
}

anonymous
()
Ответ на: комментарий от anonymous

поэтому не уверен, что работает с итераторами.

Не работает, что логично. Итератор - не список. Со слайсами «работает», но это пока экспериментальная фича, так что надо использовать найтли билды для этого, в бета-релизе не взлетит.

DarkEld3r ★★★★★
() автор топика
Ответ на: комментарий от DarkEld3r

А как вообще тогда с итераторами работать там? next() не работает, деструктуризация не работает, map() явно не достаточно для списка аргументов, так как они редко используются независимо друг от друга. Ну и деструктуризацию можно было бы и разрешить, если это не что-то вроде [other.., last].

anonymous
()
Ответ на: комментарий от anonymous

next() не работает

Работает, но требует мутабельного итератора, что в общем-то логично. Неудобно, что нет возможности получить значение «не двигая итератор дальше» (без дополнительных преобразований).

Обнаружил, что можно использовать и peekable:

println!("{}", args.peekable().peek().unwrap_or(&"EMPTY!!!".to_string()));
Сбило с толку описание - Return a reference to the next element of the iterator with out advancing it, or None if the iterator is exhausted. Возвращается как раз текущий элемент.

Идеологически это более правильно, пожалуй. Только получается даже чуть длиннее, чем уродство с take(1).last().

DarkEld3r ★★★★★
() автор топика
Последнее исправление: DarkEld3r (всего исправлений: 1)
Ответ на: комментарий от DarkEld3r

Да, кстати, может я неправильно понимаю, но разве в случае с итератором len() не вызовет проход по всем элементам? Если я правильно понимаю - итератор вместо вектора используется для того, чтобы можно было не выделять дополнительно память на массив подстрок. Вместо этого строка аргументов разбирается по мере прохода по итератору => вызов len() заставит полностью пройтись по всем аргументам. Тогда уж лучше в вектор превратить. Если честно не вижу смысла в иммутабельных итераторах. Могли бы сделать что-то вроде Iterable, который всегда возвращает новый мутабельный итератор.

anonymous
()
Ответ на: комментарий от anonymous

Можно еще так, наверно, (насчет синтаксиса опять же не уверен):

let (prog_name, other_args) = args.fold((None, ""),
        |tup, arg| match tup {
          (None, "") => (Some(arg), "")
          (f, other) => (f, other + " " + arg)
          _ => (None, "")
        }

anonymous
()
Ответ на: комментарий от Anatolik

edit: Пардон, не прочитал внимательно условия.

Кстати, ограничение в виде «cannot borrow immutable local variable as mutable» мне кажется немного дурацким, в таких случаях. Ведь даже после take(1).last() мы не сможем повторно использовать args.

DarkEld3r ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Да, кстати, может я неправильно понимаю, но разве в случае с итератором len() не вызовет проход по всем элементам?

В данном случае, у нас ExactSizeIterator, так что он знает размер без прохода.

Вместо этого строка аргументов разбирается по мере прохода по итератору => вызов len() заставит полностью пройтись по всем аргументам.

Строку они не сами разбирают, а средствами системы, так что вполне могут знать размер.

Если честно не вижу смысла в иммутабельных итераторах.

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

DarkEld3r ★★★★★
() автор топика
Ответ на: комментарий от DarkEld3r

Вот щас поставил Rust поэкспериментировать, исправил свой предыдущий код:

fn main() {
    let args = std::env::args();
    let args = args.fold((None, None), |b, arg| match b {
        (None, None) => (Some(arg), None),
        (prog_name, None) => (prog_name, Some(arg)),
        (prog_name, Some(other_args)) => (prog_name, Some(other_args + " " + &arg))
        });
    println!("{:?}", args);
    match args {
        (Some(prog_name), Some(other_args)) => {
            println!("{} {}", prog_name, other_args)
        },
        _ => println!("invalid usage")
    }
}
Это работает.

anonymous
()
Ответ на: комментарий от DarkEld3r

Ну а как иначе, если нельзя просто [head, tail..]? С другой стороны тут только 1 раз итератор используется. Лучший вариант в вектор превратить и по-моему это довольно странно, что в стандартной библиотеке список аргументов сделали иммутабельным итератором. В результате его все равно в вектор переделать проще, чем пытаться работать с итератором.

anonymous
()
Ответ на: комментарий от anonymous

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

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

DarkEld3r ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Для написания парсеров командной строки итераторы вполне удобны. А их ручной разбор с помощью сопоставления с образцом всё-таки несколько ad-hoc решение.

quantum-troll ★★★★★
()
Ответ на: комментарий от quantum-troll

Мутабельные - да. Иммутабельные? Они применимы тогда, когда соседние элементы независимы, чтобы можно было выполнить несколько преобразований через map/filter без выделения памяти под промежуточные коллекции (ну и опционально потом к результату fold или просто применить какую-то функцию к каждому элементу). В случае с командной строкой рядом идущие элементы очень часто зависимы и тут иммутабельный итератор уже не применим.

anonymous
()
Ответ на: комментарий от anonymous

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

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

quantum-troll ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.