C/C++. Способ разбора командной строки

Не так давно на работе встала передо мной задача написать прогу в среде C++ Builder, и был в ней момент, когда нужно парсить командную строку. К задаче прилагалось так же волшебное «можешь юзать все исходники, которые есть. Лежат они тут: ...». Первым делом полез, конечно, по адресу… и нашел там жутко ветвящуюся структуру кода, в которой попытался разобраться и решил, что подгонять ее под себя – ад. Поэтому пришло мне в голову написать что-то подобное Qt-шной системе сигналов и слотов, только для C++ Builder’а и аргументов командной строки.
Итак, начнем. Идея такова: анализ командной строки сводится к проверке, есть ли в аргументе спецсимвол (в моем случае – это «-» – был взять стандарт Linux). В зависимости от этого аргумент читается как имя параметра или его значение. Для вызова функций обработки используется ассоциативный массив, т.е. массив в котором в качестве ключей будут имена доступных параметров, а значений – адреса функций обработки конкретного параметра. Вот, в общем-то, и все. Приступим к реализации?

Вперед. Начнем, пожалуй, с изучения приборов. Для обработки ассоциативных массивов в C++ Builder используется тип map (объявлен в map.h)

#include <map.h>

Далее по справке. Объявим свой тип:

typedef map <AnsiString, int*> TFuncTable;

и в классе экземпляр типа:

TFuncTable funcTable;

Здесь, в качестве ключа используется AnsiString (да простит меня std::string), а значения – int* (кстати, может не прокатить для 64-битных систем). Теперь, посмотрим, как сделать insert в этот хеш. Оказалось, это не так-то просто и на это пришлось убить часа 2. А делается это так:

int *addr = (int*)&myFunction;
funcTable.insert(TFuncTable::value_type(AnsiString("-myParam"), addr));

Обратить внимание стоит на TFuncTable::value_type() – зачем так делать, я, честно сказать, так и не понял, но, видимо, это какой-то подгон типов, без которого прога даже не компилится.
Итак, проинициализировали массив – замечательно. Как он выглядеть будет? Да как-то так:

funcTable[“-myParam”] == 0xABCDEF //адрес функции-обработчика параметра

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

typedef void (*TFunc)(AnsiString param);

и придерживаемся его в написании своих обработчиков. Пример такого обработчика:

void myFunction(AnsiString name)
{
MainForm->setTestName(name);
}

Но это простой обработчик. Сложнее будет, пожалуй, когда в качестве параметра приходит что-то типа:

"param1,param2,param3"

Для реального примера можно глянуть файлик /etc/fstab – Linux.
Что же, пора рассказать, как же запустить функцию из хеша по имени параметра. Да, но перед этим, думаю, стоит собрать всю процедуру инициализации в кучу:
  1. Включили map
  2. Объявили тип-хеш, объявили экземпляр этого типа.
  3. Объявили функциональный тип и написали функции-обработчики параметров, придерживаясь этого типа.
  4. Заполнили хеш.

Теперь можно приступать к функции обработки самой командной строки. Вот так выглядит алгоритм вычисления, является ли аргумент именем параметра или его значением:

bool TMainForm::parseParams()
{
int argc;
TFunc srvFunc;
LPWSTR *cmdLine = CommandLineToArgvW(GetCommandLineW(), &argc);
int iter = 1;

for(int i = 1; i < argc; i += iter)
{
AnsiString param = "";
AnsiString paramName = cmdLine[i];
if(i < argc - 1)
{
if(AnsiString(cmdLine[i + 1])[1] == '-')
iter = 1;
else
{
param = cmdLine[i + 1];
iter = 2;
}
}

if(funcTable.find(paramName) == funcTable.end())
{
Out("Неизвестный параметр " + paramName);
return false;
}
srvFunc = (TFunc)funcTable[paramName];
srvFunc(param);
}
return true;
}


Что есть что:
LPWSTR *cmdLine = CommandLineToArgvW(GetCommandLineW(), &argc); — с этим в борландовской справке тоже все довольно мутно, но сводится все к тому, что такая последовательность преобразований выдает стандартные argc и argv, поэтому идем дальше.
Out() — специализированная функция ввода / вывода. Для заданной темы никакого интереса не представляет.
Процедуру поиска символа «-» в аргументе строки, думаю, рассказывать не надо – она банальна. Самое интересное тут:

srvFunc = (TFunc)funcTable[paramName];
srvFunc(param);

Так запускается функция обработчик. Просто, не правда ли? И никаких диких ветвлений до 1000-го колена.

Итак, что мы имеем? Работаю я в гос. конторе, и именно поэтому задерживаться там не собираюсь. То есть, пришедший после меня работник с легкостью напишет свои обработчики, загонит адреса в хеш, а насчет анализатора командной строки ему думать не придется. Вот такая арифметика.
Поделиться публикацией
Похожие публикации
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 12
  • +5
    Очередной велосипед? Есть классический gengetopt для этих целей. Но перспективнее boost::program_options.
    • 0
      Спасибо. В следующий раз буду копать в указанную сторону.
      • +1
        Слава Богу, я то уж испугался, что getopt() больше не существует.
        Признайтесь, а Вы о нем знали?
        • 0
          Признаюсь, не знал. Мне вообще попал в руки парсер с кучей if-ов
    • +1
      Просмотрел код, я так понимаю ваш разборщик умеет парсить только простые параметры, без переменных, т.е. напрмиер строку типа
      dd if=~/some.iso of=/dev/sdb
      или
      mysqldump --user=root --password=pass --databases mydb1

      он не разберет, какой в нем тогда смысл? Простые параметры парсить вообще не зачем — читай себе argv и читай, вот и весь парсинг
      • 0
        Вот если вы прочитаете внимательно последний абзац, то, наверно, поймете, что главной целью этого алгоритма является — передача по наследству. Для каждой программы, решающей подобную задачу, набор параметров разный, и каждый раз копаться в сильноветвящейся структуре из условных конструкций, мягко говоря, неудобно.
        Парсить сложные строки, по задаче, не надо. Стандарт оформления был взят из man gcc.
        • 0
          Если это не универсальное решение, а просто решения для проблем какой-то одной компании и одного программиста, то какой смысл об этом рассказывать? Все что вы показали. это то что можно хранить указатели на функции в контейнере, ну так это базовая возможность языка, любой, кто прочитал хотя бы пару книжек по С++, об этом знает.
          Еще мне не понятно, о какой сильно ветвящейся структуре вы говорите, если вы можете отобразить ее в линейный список, по вашему коду видно, что на один параметр вызывается один метод, он не подходит для случаев композиции параметров, т.е. нельзя используя ваш подход сделать что-то типа:
          «Если присутсвует параметр -p параметр -l выполняет операцию L1 иначе L2», либо вы будете вынуждены в некотором промежуточном методе L выяснять присутствует ли в списке параметр -p и на основе этого выбирать поведение L1 или L2 — а этот означает использование конструкции ветвления. Т. е. в таком случае, вы сможете избежать только верхнего слоя условий.
          • 0
            Согласен, условие будет, но оно не будет вложено во вложенное, вложенное во вложенное…
            Да и вообще, наша с вами дискуссия потеряла смысл, так как комментом выше выяснилось, что я поспешил и не подумал о том, что есть готовые решения для данной задачи.
      • 0
        Не понял, в вашем случае argv[] чем не устраивает?
        • 0
          funcTable.insert(TFuncTable::value_type(AnsiString("-myParam"), addr));
          А чем не устроил такой вариант: funcTable["-myParam"] = addr?
          P.S. Осильте, плз, форматирование кода
          • 0
            Не знаю почему, но такой вариант не работал.
            • 0

              Может быть уже и не надо, тоже была задача код с Java в С++ CLI перекодить, тоже думал про boost (т.к. нужен был cross-paltform) или велосипед. В итоге не хотелось из-за этой не большой таски boost тащить и сделал велосипед :), т.к. хотелось полегче и попроще все-таки. Парсит вроде всё, опции, параметры, подкоманды, вот исходники, если еще интересно можете посмотреть, поюзать: https://github.com/gera-gas/ApplicationHelpers/tree/master/Source/CmdLine

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