Уголок линуксоида: программируем библиотеку пользовательских команд

 | 12.09

Мой Компьютер, №8, 14.02.2008

Благодаря рассматриваемой библиотеке, можно в некоторой мере автоматизировать рутинную работу, связанную с вводом команд пользователя. Но обработка ввода — это далеко не главная и совсем не единственная особенность библиотеки. Она позволяет гибко настраивать программу, беря на себя практически всю заботу о сборе данных от пользователя. Программисту остается только получить эти данные с помощью средств библиотеки и соответствующим образом изменить поведение программы. Да, и не лишним будет знать просто о том, что есть такая библиотека для программирования в консоли — хотя бы чтоб поддержать светскую беседу :-). Сразу отмечу, что дополнительную информацию про эту библиотеку можно получить с помощью man readline, info readline, да и Гугл никто не отменял :-), так что в статье я буду использовать только основные сведения. Если все же останутся вопросы, мыльте, чем смогу — помогу.

Итак, предлагаю заняться разработкой (громко звучит :-)) консольного приложения. Для начала сделаем «заготовку» программы — никакой полезной работы она выполнять не будет, но все недостающее легко будет добавить самостоятельно. Кстати, писать будем на С/С++ (при использовании библиотеки разница не принципиальна, отличаться будет только компилятор: g++ — для «плюсов» и gcc — для С, но все-таки предпочтение отдаю плюсам, тем более что я уже привык к потоковому вводу/выводу).

(Примечание автора: хотел бы извиниться за столь подробное изложение казалось бы простых моментов, но статья учитывает возможности начинающих пользователей — «паверъюзеры» наверняка знакомы с этой библиотекой, а если и не знакомы, то разобраться в ней самостоятельно не составит особых усилий.)

С самого начала предлагаю установить библиотеку readline для разработки (те, у кого она установлена, естественно, могут пропустить этот шаг).

Для моего Кubuntu 7.04 (feisty) хватило следующих команд:

apt-get install libncurses5-dev libreadline5-dev

или

sudo apt-get install libncurses5-dev libreadline5-dev

(если вы не суперпользователь).

Скачивается, если я не ошибаюсь, порядка 1 Мб, что сегодня не требует сверхусилий, на мой взгляд.

Итак, что же такое readline, и с чем его едят. Для начала спросим официальную справку (man readline). Оттуда узнаем, что для того, чтобы пользоваться библиотекой, необходимо подключить заголовочный файл:

#include <readline/readline.h>

(Указанный следом #include <readline/history.h> — для поддержки истории в вашей программе. Тут все совсем просто, смотрите справку. Но если останутся вопросы — пишите.)

Оттуда же узнаем и об основных недостатках библиотеки: в разделе «BUGS» читаем, что «It’s too big and too slow» :-).

Как видим дальше, возможностей довольно много, но сегодня нас будет интересовать лишь одноименная функция, прототип которой описывается следующим образом:

char *readline(const char *prompt)

Что же, собственно, делает эта функция? Она всего-навсего возвращает указатель на строку, введенную пользователем с stdin (входной поток, в большинстве случаев — клавиатура; но ничего не помешает вам подать на вход вашей программы файл с командами — достаточно использовать перенаправление вывода, но это я уже отвлекся :-)) после приглашения prompt. Следует отметить, что выделение памяти происходит внутри функции, но ее освобождение после завершения работы — на совести программиста.

Но вернемся к функции. На первый взгляд, все слишком просто, но давайте не будем забывать об ознакомительном характере нашей программы. Если хотите взглянуть на документацию (пропишите сами в поисковике что-то вроде «GNU Readline Library»), то там есть пример. Немного упрощенную его версию я сейчас и приведу в качестве демонстрации работы этой библиотеки, а затем прокомментирую основные моменты.

(Для набора исходного текста программы я лично использую joe, но, как говорится, «на вкус и цвет…»).

#include <iostream>

using namespace std;

#include <readline/readline.h> //подключаем нашу библиотеку

//здесь — наши функции, вызываемые по определенной команде

int action() { cout<<«Some action.»<<endl; }

int help() { cout<<«[HELP] Commands: action, help, about, quit»<<endl; }

int about() { cout<<«[ABOUT] Readline library example»<<endl; }

int quit() { exit(0); }

//описание структуры «команда»: имя команды, указатель на функцию-обработчик и дополнительное поле (его можно использовать для справочной информации о функции, например; в нашей программе не используется)

typedef struct

{

 char *name;

 rl_hook_func_t *func;

 char *doc;

} COMMAND;

//здесь объявляем массив команд — собственно, множество команд, распознаваемых программой

COMMAND commands[]={

 { «action», action, «action description» },

 { «help», help, «help description» },

 { «about», about, «about description» },

 { «quit», quit, «just quit» },

 { (char*)NULL, (rl_hook_func_t*)NULL, (char*)NULL }

};

//прототип функции, отвечающей за распознавание и обработку введенной команды

COMMAND *getcommand(char*);

//выполнение программы начинается с main

int main(int argc, char **argv)

{

 char *line;

//указатель на массив символов — буфер для информации, введенной пользователем

 cout<<«Readline library example by SM-pro»<<endl;

 while (1) //работаем, пока не получим команду «выход»

 {

    line = readline(«[readline example]$ «);

//получаем информацию от пользователя

     COMMAND *command;

     command = getcommand(line); //распознаем команду

     if ( command )

        command->func(); //вызываем соответствующий обработчик…

     else

        cout<<«[ERROR] No such command!»<<endl; //…или сообщаем, что команда не распознана — ошибка ввода

 free(line); //освобождение памяти, выделенной под буфер для входных данных от пользователя

 }

 return 0;

}

//здесь — распознавание и обработка команды

COMMAND *getcommand(char* name)

{

 //проверяем все обрабатываемые команды на предмет совпадения имени команды с введенной пользователем строкой

 for (int i=0; commands[i].name; i++)

    //если находим совпадение — возвращаем указатель на элемент массива команд

     if ( !strcmp(name, commands[i].name) )

      return ( &commands[i] );

 //если совпадение не обнаружено — возвращаем нулевой указатель

 return ( (COMMAND*)NULL );

}

Итак, что же собственно делает эта нехитрая программа и как? В начале указываем препроцессору, что надо включить заголовочный файл readline.h, который лежит в каталоге readline. Потом мы объявляем некоторый набор функций — это обработчики команд. На этих строках, правда, они еще просто глобальные функции, но все еще впереди. Дальше мы определяем структуру — тип нашей команды. Она состоит из трех полей: первое поле — собственно имя команды, при обработке которой будет вызываться функция, адрес которой задан во втором поле. И третье поле — некоторое описание команды (в нашей программе нигде не используется). А про второе поле следует сказать еще пару слов. Дело в том, что в используемой библиотеке (точнее, в подключенном заголовочном файле) определен некоторый набор типов — указателей на функции, и мы из этого набора используем только тип rl_hook_func_t, что соответствует int (*)(). Подробнее о наборе этих типов можно узнать из документации (ну, к примеру, rl_command_func_t соответствует int (*)(int, int) и т.д.).

На этом и заканчивается объявление набора команд, обрабатываемых нашей программой. Далее идет прототип функции, описание которой находится в конце листинга. В функции getcommand мы ищем совпадение между введенной строкой и именами наших команд (именно команд, а не функций-обработчиков, просто в данном примере они совпадают), и если находим, то возвращаем указатель на введенную команду (указатель на структуру с описанием данной команды). И если не находим — возвращаем ноль (для этого случая и нужен последний элемент из массива структур commands). Потом освобождаем память, выделенную под указатель line. Дальше идет функция main, где собственно и происходит все это безобразие :-). Здесь мы спрашиваем у пользователя команду до тех пор, пока не встретим команду выхода, а если введенная команда не найдена, сообщаем об этом. Не стоит также забывать про освобождение памяти, которая выделяется для хранения строки — это, как уже упоминалось, следует делать самостоятельно.

Теперь сохраняем это в файл, например, read.h и компилируем следующим образом:

g++ read.cpp -l readline -o read

Где -l readline и есть подключение нужной нам библиотеки. Теперь запускаем:

./read

И любуемся результатом.

Вот, собственно, и все, о чем я хотел рассказать. Кому-то может показаться, что это все слишком просто…

Так и есть :-), но напомню, что это лишь демонстрация того, что можно делать в консоли. Как видите, здесь тоже есть готовые решения — не нужно каждый раз изобретать велосипед.

Удачи!

Сергей «sm-pro» МЕЛЬНИЧУК

Robo User
Robo User
Web-droid editor