Работа с commons-cli 1.2

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

Дабы не изобретать велосипед, решил взять готовую библиотеку. Выбор остановил на commons-cli, для нее удалось найти пару примеров и использование ее казалось не очень уж сложны.

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

Итак, начнем. В основе Commons-cli лежит понятие опции. Чтобы сразу внести ясность, попробуем определиться с терминологией. Рассмотрим такую строчку:

Text.exe –l login ––password 123456

Происходит вызов программы test.exe с использованием опции l, которая в качестве аргумента принимает строку «login» и с использованием опции password, с аргументом в виде строки «123456». Нетрудно догадаться, что программа требует аутентификации, опция l отвечает за логин, а опция password само собой за пароль. Для простоты можно пару «опция, аргумент» рассматривать классичским способом «параметр=значение».

Кстати, форма записи со знаком «=» вполне допустима при использовании commons-cli.

Опции могут быть без аргументов вовсе или аргументов может быть больше одного. Если имя опции состоит из 1 буквы, то ей предшествует «-» (назовем это, по простому «тире»). Если имя опции состоит из 2 и более букв то необходимо удваивать «тире», как с опцией password.

С терминологией разобрались, можно переходить непосредственно к коду. Итак, все начинается с опций. Создаем опцию:

Option option = new Option("l", "login", true, "Login"); 


Коснтруктор в данном случае принимает 4 параметра: короткую форму опции (однобуквенная опция), длинную форму опции, флаг обозночающий наличие параметров и текстовое пояснение данной опции. Мы могли указать только однобуквнное предствления опции или только расширенное представлении опции, в каждом из этих случае конструктор принимал бы 3 параметра, но не указывать ни многобуквенной ни однобуквенной опции нельзя, необходимо указать хотя бы одну. Затем необходимо определить, как данная опция будет работать с аругментами. Примерно так:

option.setArgs(1); // число аргументов в опции
option.setOptionalArg(false);// являются ли аргументы необязательными для ввода, по умолчанию аргументы обязательны для ввода, так что эту строчку можно было опутить
option.setArgName("login ");//имя аргумета, именно так аргумент будет отображатся в справка по использованию командной строки.


Вроде бы из комментариев к коду все должно быть понятно. Если нам необходима опция без аргументов (опция-флаг), то ставим setArgs(0). После создания опции необходимо добавить ее в объект Options.

Options posixOptions = new Options();
posixOptions.addOption(option);


Теперь необходимо создать парсер командной строки и снабдить его необходимой информацией для работы:

CommandLineParser cmdLinePosixParser = new PosixParser();// создаем Posix парсер
CommandLine commandLine = cmdLinePosixParser.parse(posixOptions, commandLineArguments);// парсим командную строку


Для начала создаем парсер, полновесных парсера в commons-cli 2 – Posix парсер и GNU парсер. Скажу честно, в их различия не вникал, но по беглому осмотру мне приглянулся Posix-парсер (регламентируется Posix стандартом, соотвественно будет работать на всех система поддерживающих этот стандарт).Затем запускаем парсер, в качестве параметров метод parse принимает коллекцию опций в виде объекта Options, и собственно строку с параметрами которые были переданны вашей программе при запуске (само собой что стандартный массив args[] перед использованием необходимо объекденить в одну строку разделяя элементы пробелами). Разультат разбора будет возвращен в объект commandLine.

Теперь необходимо выполнить полученные команды, делается это вот так:

if (commandLine.hasOption(“l”)) { // проверяем, передавали ли нам команду l, сравнение будет идти с первым представлением опции, в нашем случаее это было однобуквенное представление l
	String[] arguments = commandLine.getOptionValues(“l”);// если такая опция есть, то получаем переданные ей аргументы
	System.out.println("We try to Login with: " + arguments[0]);// выводим переданный логин
	… // работаем дальше
}


Как видно, работа с commons-cli довольно просто. К тому же данная библеотека возьмет на себя заботы по выводу справки по использованию программы, не полностью, но многое облегчит:

public static void printHelp(
	final Options options,
	final int printedRowWidth,
	final String header,
	final String footer,
	final int spacesBeforeOption,
	final int spacesBeforeOptionDescription,
	final boolean displayUsage,
	final OutputStream out)
	{
		final String commandLineSyntax = "java test.jar";//подсказка по запуску самой программы
		final PrintWriter writer = new PrintWriter(out);// куда печатаем help
		final HelpFormatter helpFormatter = new HelpFormatter();// создаем объект для вывода help`а
		helpFormatter.printHelp(
		writer,
		printedRowWidth,
		commandLineSyntax,
		header,
		options,
		spacesBeforeOption,
		spacesBeforeOptionDescription,
		footer,
		displayUsage);//формирование справки
		writer.flush(); // вывод
	}

Чтобы все стало ясно, приведу пример вызова данного метода:

printHelp(
	posixOptions, // опции по которым составляем help
	80, // ширина строки вывода
	"Options", // строка предшествующая выводу
	"-- HELP --", // строка следующая за выводом
	3, // число пробелов перед выводом опции 
	5, // число пробелов перед выводом опцисания опции
	true, // выводить ли в строке usage список команд
	System.out // куда производить вывод
);


Пожалуй здесь все понятно, кроме термина «строка usage». Приведу пример пример, из которого станет ясно как использовать данный параметр. При значении true вывод будет примерно такой:

usage: java test.jar [-l ] [-h]

При значении false, вывод будет таким:

usage: java test.jar

Думаю разница понятна.

Последнее — это группы опций. Например, есть две опции -a и -b, они являются взаимоисключающими, то есть одновременно не могут быть указаны. Для такого случая и создаются группы опций:

OptionGroup optionGroup = new OptionGroup();
optionGroup.addOption(new Option("a", true, "A option");
optionGroup.addOption(new Option("b", true, "B option");
posixOptions.addOptionGroup(optionGroup);

Тоже все довольно просто.

В общем бибилиотека оставила неплохие впечатления по использованию, но очень быстро ее стало не хватать. Текущая версия бибилиотеки 1.2, при этом идет развитие абсолютно новой версии 2.0 – собственно, по описанию api версии 2.0 можно сказать, что эта версия должна удовлетворить большинство потребностей предъявляемых к библиотекам cli.

Плюсы commons-cli:
  • простота использования
  • группы опций
  • длинные и короткие нотации опций (-l, --login)
  • возможность парсить опции следующего вида -ab, когда есть две опции -a, -b

Минусы (из собственных потребностей, а так их конечно больше):
  • нет возможности создавать подкоманды (как в git есть команды branch, status и т.д. со своими опциями)
  • не очень удобная работа с аргументами опции, если их больше 1

В итоге, для простенькой cli — отличное решение, что-то более серьезное искать альтернативы или ждать версию 2.0.

Ссылки в статье:


UPD. По подсказке nord_ua исправил ссылку на Posix стандарт командной строки.
Исправил опечатки и добавил ссылку на commons-cli. Спасибо nik_lazarev и FractalizeR.
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 19

    +1
    Спасибо за обзор, было интересно ознакомиться.
      +1
      Спасибо за положительный отзыв. Если топик будет интересен, есть мысль задокументировать свои изыскания в поисках подходящей cli, который предшествовал выбору в пользу common-cli.
      +1
      А почему не arg4j? Или по условиям задачи нужна была совместимость с java меньше 5-ки.
        +1
        Нет, такого условия не стояло. Впервые столкнулся с проблемой действительно необходимого большого количества команд, соотвественно впервые искал cli библиотеку. Поискал, что есть на этот счет в сети (кстати на arg4j не наткнулся), выбирал по критерию простота освоения/документация/наличие примеров. common-cli оказалась лучше по этому критерию. А arg4j посмотрю.
          +1
          А еще есть творение Cedric Buest'а — JCommander.
            0
            JCommander удивил своей простотой. Действительно минимум кода, функционал попробую повнимательней присмотреть.
              0
              И практически сразу обнаружил в нем поддержку нескольких команд со своими опциями. Вроде все что нужно, кроме суровой специфики разве что какой, здесь есть.
          +2
          А мне jopt-simple понравился, простой и примеров много на сайте
            0
            Тоже и прост, и функционал неплох. Спасибо за ссылочку.
            0
            На всякий случай спрошу: какие аналоги есть для С/С++?
            +1
            Передо мной недавно стояла похожая задача, и я выбрал JewelCli. Плюсы:
            1) у опций может быть много аргументов (т.е., значение опции может быть списком).
            2) у common-cli я не нашел способа указать список файлов в качестве аргументов (т.е., «хвост» опций, которые передаются как есть). В JewelCli это есть.
            3) В JewelCli проще задавать набор опций — описывается интерфейс (с нужными свойствами), и парсер аргументов возвращает экземпляр этого интерфейса
            4) JewelCli парсит аргументы, а не отдает их в виде строки
              0
              1) в common-cli может быть тоже много аргументов, к сожалению работа с ними не очень удобна. Очевидно разрабатывалось все под один аргумент, но возможность работать с несколькими аргументами есть.
              2) не совсем уверен, что все правильно понял, например команда --file 1.txt 2.txt 3.txt в common-cli будет работать, о чем я написал в п.1, не очень удобно но будет. Возможно речь идет о переменном числе аргументов, например список файлов может быть и из 2 и из 3 и из 10 имен — здесь да, косяк.
              3) ну вопрос «проще/сложнее» скользкий, я бы предпочел формулировку — код компактнее, лаконичнее — более измеряемые понятия. При их применении — согласен с вами.
              4) Согласен.

              Накидали приличное число ссылок на cli-библиотеки. Свести их ± в единую табличку в новом посте что ли?
                0
                сорри за некропост, но может кому поможет:
                если переменное число аргументов то можно указать что их неограниченное кол-во:
                option.setValueSeparator(';'); option.setArgs(Option.UNLIMITED_VALUES);
              +1
              Вы бы хоть ссылок дали на этот common-cli. Поскольку я подозреваю, что это вовсе не common-cli, а commons-cli от Apache. Это так?
                0
                Ваша правда, упущенный s — не внимательность, а вот ссылка потерялась при переносе из песочницы публикации. Спасибо за поправку.
                +1
                Спасибо, очень содержательно.
                Было бы также интересно получить сравнительный анализ аналогичных библиотек (вы ведь его commons-cli не просто так выбрали).
                  0
                  Да, перед этим просматривал несколько библиотек. Уже возникла мысль о подобном обзоре, попробую сделать такой.

                Only users with full accounts can post comments. Log in, please.