说明

GNU Readline是一个跨平台开源程序库,提供交互式的文本编辑功能。应用程序借助该库函数,允许用户编辑键入的命令行,并提供自动补全和命令历史等功能。Bash(Bourne Again Shell)、GDB、ftp 和mail等程序就使用Readline库提供其命令行界面。

Readline是GNU通用公共许可证版本3(GNU GPLv3)下的自由软件。这意味着若发布或分发的程序使用Readline库,则该程序必须是自由软件,并持有GPL-兼容的许可证。当然,用户也可使用自己的实现替代库的行为。

本文将基于若干典型的Readline库函数,给出一个简单的交互式调测器示例。示例代码的运行环境如下:

一  函数介绍

本节简要介绍后文将要用到的几个Readline库函数,如用来替代fgets()的readline()函数。关于库函数的更多描述可参考http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html

readline()函数的ANSI C声明如下:

char *readline(const char *prompt);

该函数打印参数prompt 所指的提示字符串,然后读取并返回用户输入的单行文本(剔除换行符)。若prompt 为空指针或指向空字符串,则不显示任何提示。若尚未读取到字符就遇到EOF,则该函数返回NULL;否则返回由malloc()分配的命令行内存,故调用结束后应通过free()显式地释放内存。

读取命令行后,可调用add_history()函数将该行存入命令行历史列表中,以便后续重取。

void add_history(const char *string);

rl_completion_matches()函数用于自动补全:

typedef char *rl_compentry_func_t(const char *text, int state);

char **rl_completion_matches(const char *text, rl_compentry_func_t *entry_func);

该函数返回一个字符串数组,即参数text的补全列表;若没有补全项,则函数返回NULL。该数组以空指针结尾,首个条目将替换text,其余条目为可能的补全项。函数指针entry_func指向一个具有两个参数的函数。其中参数state在首次调用时为零,后续调用时为非零。未匹配到text时,entry_func返回空指针。

函数指针rl_completion_entry_function指向rl_completion_matches()所使用的补全生成函数:

rl_compentry_func_t *rl_completion_entry_function;

若其值为空,则使用默认的文件名补全生成函数,即rl_filename_completion_function()。

应用程序可通过函数指针rl_attempted_completion_function自定义补全函数:

typedef char **rl_completion_func_t(const char *text, int start, int end);

rl_completion_func_t * rl_attempted_completion_function;

该变量指向创建匹配补全的替代函数。函数参数 start和 end为字符串缓冲区rl_line_buffer的下标,以定义参数text的边界。若该函数存在且返回NULL,或者该变量值被设置为NULL,则rl_complete()将调用rl_completion_entry_function指向的函数来生成匹配结果;除此之外,程序使用该变量所指函数返回的字符串数组。若该变量所指函数将变量rl_attempted_completion_over设置为非零值,则Readline将不执行默认的文件名补全(即使自定义补全函数匹配失败)。

二  示例代码

首先,自定义用户命令结构及其执行函数(为简化实现,执行函数仅打印函数名):

 //命令结构体
typedef int (*CmdProcFunc)(void);
typedef struct{
char *pszCmd;
CmdProcFunc fpCmd;
}CMD_PROC; //命令处理函数定义
#define MOCK_FUNC(funcName) \
int funcName(void){printf(" Enter "#funcName"!\n"); return ;} MOCK_FUNC(ShowMeInfo);
MOCK_FUNC(SetLogCtrl);
MOCK_FUNC(TestBatch);
MOCK_FUNC(TestEndianOper);

基于上述定义,创建命令列表如下:

 //命令表项宏,用于简化书写
#define CMD_ENTRY(cmdStr, func) {cmdStr, func}
#define CMD_ENTRY_END {NULL, NULL} //命令表
static CMD_PROC gCmdMap[] = {
CMD_ENTRY("ShowMeInfo", ShowMeInfo),
CMD_ENTRY("SetLogCtrl", SetLogCtrl),
CMD_ENTRY("TestBatch", TestBatch),
CMD_ENTRY("TestEndian", TestEndianOper), CMD_ENTRY_END
};
#define CMD_MAP_NUM (sizeof(gCmdMap)/sizeof(CMD_PROC)) - 1/*End*/

然后,提供两个检索命令列表的函数:

 //返回gCmdMap中的CmdStr列(必须为只读字符串),以供CmdGenerator使用
static char *GetCmdByIndex(unsigned int dwCmdIndex)
{
if(dwCmdIndex >= CMD_MAP_NUM)
return NULL;
return gCmdMap[dwCmdIndex].pszCmd;
} //执行命令
static int ExecCmd(char *pszCmdLine)
{
if(NULL == pszCmdLine)
return -; unsigned int dwCmdIndex = ;
for(; dwCmdIndex < CMD_MAP_NUM; dwCmdIndex++)
{
if(!strcmp(pszCmdLine, gCmdMap[dwCmdIndex].pszCmd))
break;
}
if(CMD_MAP_NUM == dwCmdIndex)
return -;
gCmdMap[dwCmdIndex].fpCmd(); //调用相应的函数 return ;
}
     以上代码独立于Readline库,接下来将编写与该库相关的代码。

考虑到实际应用中,程序运行平台不一定包含Readline库,因此需要用__READLINE_DEBUG条件编译控制库的使用。

 #ifdef __READLINE_DEBUG
#include <readline/readline.h>
#include <readline/history.h> static const char * const pszCmdPrompt = "clover>>"; //退出交互式调测器的命令(不区分大小写)
static const char *pszQuitCmd[] = {"Quit", "Exit", "End", "Bye", "Q", "E", "B"};
static const unsigned char ucQuitCmdNum = sizeof(pszQuitCmd) / sizeof(pszQuitCmd[]);
static int IsUserQuitCmd(char *pszCmd)
{
unsigned char ucQuitCmdIdx = ;
for(; ucQuitCmdIdx < ucQuitCmdNum; ucQuitCmdIdx++)
{
if(!strcasecmp(pszCmd, pszQuitCmd[ucQuitCmdIdx]))
return ;
} return ;
} //剔除字符串首尾的空白字符(含空格)
static char *StripWhite(char *pszOrig)
{
if(NULL == pszOrig)
return "NUL"; char *pszStripHead = pszOrig;
while(isspace(*pszStripHead))
pszStripHead++; if('\0' == *pszStripHead)
return pszStripHead; char *pszStripTail = pszStripHead + strlen(pszStripHead) - ;
while(pszStripTail > pszStripHead && isspace(*pszStripTail))
pszStripTail--;
*(++pszStripTail) = '\0'; return pszStripHead;
} static char *pszLineRead = NULL; //终端输入字符串
static char *pszStripLine = NULL; //剔除前端空格的输入字符串
char *ReadCmdLine()
{
//若已分配命令行缓冲区,则将其释放
if(pszLineRead)
{
free(pszLineRead);
pszLineRead = NULL;
}
//读取用户输入的命令行
pszLineRead = readline(pszCmdPrompt); //剔除命令行首尾的空白字符。若剔除后的命令不为空,则存入历史列表
pszStripLine = StripWhite(pszLineRead);
if(pszStripLine && *pszStripLine)
add_history(pszStripLine); return pszStripLine;
} static char *CmdGenerator(const char *pszText, int dwState)
{
static int dwListIdx = , dwTextLen = ;
if(!dwState)
{
dwListIdx = ;
dwTextLen = strlen(pszText);
} //当输入字符串与命令列表中某命令部分匹配时,返回该命令字符串
const char *pszName = NULL;
while((pszName = GetCmdByIndex(dwListIdx)))
{
dwListIdx++; if(!strncmp (pszName, pszText, dwTextLen))
return strdup(pszName);
} return NULL;
} static char **CmdCompletion (const char *pszText, int dwStart, int dwEnd)
{
//rl_attempted_completion_over = 1;
char **pMatches = NULL;
if( == dwStart)
pMatches = rl_completion_matches(pszText, CmdGenerator); return pMatches;
} //初始化Tab键能补齐的Command函数
static void InitReadLine(void)
{
rl_attempted_completion_function = CmdCompletion;
} #endif

自动补全后的命令字符串结尾多出一个空格,故需调用StripWhite将该空格剔除。

最后,可编写交互式调测函数如下:

 int main(void)
{
#ifndef __READLINE_DEBUG
printf("Note: Macro __READLINE_DEBUG is Undefined, thus InteractiveCmd is Unavailable!!!\n\n");
#else
printf("Note: Welcome to Interactive Command!\n");
printf(" Press 'Quit'/'Exit'/'End'/'Bye'/'Q'/'E'/'B' to quit!\n\n");
InitReadLine();
while()
{//也可加入超时机制以免忘记退出
char *pszCmdLine = ReadCmdLine();
if(IsUserQuitCmd(pszCmdLine))
{
free(pszLineRead);
break;
} ExecCmd(pszCmdLine);
}
#endif return ;
}

该函数用法类似Shell,便于定制调测命令的随机执行。命令中首个参数(本文参数唯一)支持自动补全,但参数区分大小写。

编译链接时需加载readline库和termcap(或ncurses)库。ncurses库通常使用terminfo(终端信息),少数实现会使用termcap(终端能力)。启用Readline库时,执行结果如下:

 [wangxiaoyuan_@localhost test1]$ gcc -Wall -o ReadLine ReadLine.c -D__READLINE_DEBUG -lreadline -lncurses
[wangxiaoyuan_@localhost test1]$ ./ReadLine
Note: Welcome to Interactive Command!
Press 'Quit'/'Exit'/'End'/'Bye'/'Q'/'E'/'B' to quit! clover>>ShowMeInfo(完整输入)
Enter ShowMeInfo!
clover>>ShowMeInfo(UP键调出历史命令)
Enter ShowMeInfo!
clover>>SetLogCtrl (输入'Se'自动补全)
Enter SetLogCtrl!
clover>> TestEndianOper(错误输入)
clover>>TestEndian (输入'T'自动补全为"Test",再输入'E'自动补全为"TestEndian ")
Enter TestEndianOper!
clover>> TestBatch (命令首尾加空格,无法自动补全)
Enter TestBatch!
clover>>ReadLine (输入'R'自动补全文件名)
clover>>quit
[wangxiaoyuan_@localhost test1]$

不启用Readline库时,执行结果如下:

 [wangxiaoyuan_@localhost test1]$ gcc -Wall -o ReadLine ReadLine.c
ReadLine.c:41: warning: 'GetCmdByIndex' defined but not used
ReadLine.c:49: warning: 'ExecCmd' defined but not used
[wangxiaoyuan_@localhost test1]$ ./ReadLine
Note: Macro __READLINE_DEBUG is Undefined, thus InteractiveCmd is Unavailable!!!

GNU Readline库函数的应用示例的更多相关文章

  1. GNU Readline 库及编程简介

    用过 Bash 命令行的一定知道,Bash 有几个特性: TAB 键可以用来命令补全 ↑ 或 ↓ 键可以用来快速输入历史命令 还有一些交互式行编辑快捷键: C-A / C-E 将光标移到行首/行尾 C ...

  2. GNU Readline 库及编程简介【转】

    转自:https://www.cnblogs.com/hazir/p/instruction_to_readline.html 用过 Bash 命令行的一定知道,Bash 有几个特性: TAB 键可以 ...

  3. rlwrap安装报错You need the GNU readline 解决方法

    首先大家肯定知道rlwrap是干什么的? 在linux以及unix中,sqlplus的上下左右.回退无法使用,会出现乱码情况.而rlwrap这个软件就是用来解决这个的.   这个错误曾经困扰我很久很久 ...

  4. aio 系列函数是由 POSIX 定义的异步操作接口,可惜的是,Linux 下的 aio 操作,不是真正的操作系统级别支持的,它只是由 GNU libc 库函数在用户空间借由 pthread 方式实现的,而且仅仅针对磁盘类 I/O,套接字 I/O 不支持。

    30 | 真正的大杀器:异步I/O探索 https://time.geekbang.org/column/article/150780

  5. Python2.7字符编码详解

    目录 Python2.7字符编码详解 声明 一. 字符编码基础 1.1 抽象字符清单(ACR) 1.2 已编码字符集(CCS) 1.3 字符编码格式(CEF) 1.3.1 ASCII(初创) 1.3. ...

  6. Linux->Windows主机目录和文件名中文乱码恢复

    目录 Linux->Windows主机目录和文件名中文乱码恢复 声明 一. 乱码问题 二. 调试环境 三. 目录和文件名乱码恢复 3.1 可选方案 3.1.1 通过合适的编解码转换 3.1.2 ...

  7. 转1:Python字符编码详解

    Python27字符编码详解 声明 一 字符编码基础 1 抽象字符清单ACR 2 已编码字符集CCS 3 字符编码格式CEF 31 ASCII初创 311 ASCII 312 EASCII 32 MB ...

  8. 使用pyenv在系统中安装多个版本的python

    pyenv的安装与使用 如果没有安装git,首先要安装git apt-get install git 安装完成后,使用自动安装程序提供的单行程进行安装: curl -L https://github. ...

  9. PHP的命令行扩展Readline相关函数学习

    PHP 作为一个 Web 开发语言,相对来说,命令行程序并不是它的主战场.所以很多年轻的 PHP 开发者可能连命令行脚本都没有写过,更别提交互式的命令操作了.而今天,我们带来的这个扩展就是针对 PHP ...

随机推荐

  1. Git -- 基本操作 之 版本回退

    现在,你已经学会了修改文件,然后把修改提交到Git版本库,现在,再练习一次,修改readme.txt文件如下: Git is a distributed version control system. ...

  2. MOD 10,11算法(GB/T 17710-1999 数据处理 校验码系统 ),使用javascript实现

    原文链接:http://chunniu.info/p/74.html GB/T 17710-1999 数据处理 校验码系统 ,便于使用,使用javascript做了一个页面 [php] var NUM ...

  3. MySQL 数据库定时自动备份

    创建备份目录 cd /home mkdir backup cd backup 创建备份 Shell 脚本: vim DatabaseName.sh #!/bin/bash /usr/local/mys ...

  4. oracle 杀掉当前用户的进程

    select ' alter system kill session '''|| sid ||','||serial# || ''';' from v$SESSION where username=' ...

  5. linux中wget命令

    Linux系统中的wget是一个下载文件的工具,它用在命令行下.对于Linux用户是必不可少的工具,我们经常要下载一些软件或从远程服务器恢复备份到本地服务器.wget支持HTTP,HTTPS和FTP协 ...

  6. Caffe 学习:Eltwise层

    Eltwise层的操作有三个: 1. PROD(product):按元素乘积 2. SUM:按元素求和(默认操作) 3. MAX:保存元素大者

  7. CentOS 经常使用系统命令

    # uname -a # 查看内核/操作系统/CPU信息# head -n 1 /etc/issue   # 查看操作系统版本号# cat /proc/cpuinfo      # 查看CPU信息# ...

  8. koa2使用注意点总结

    post请求,ajax传入的参数获取的时候为ctx.request.body get请求,ajax传入参数获取的时候为ctx.request.query.参数名 koa-csrf可以设置什么请求的时候 ...

  9. 百度云高速下载Pandownload

    对于一些文件大小比较小的文件,可以直接在网页分享中点击[下载]来下载: 但是,对于较大点的文件,点击[下载]会弹出百度云的桌面客户端软件来下载: 但但是,下载速度实在是太慢了,强迫症真真等不及啊~ 幸 ...

  10. 解决Spring Boot中,通过filter打印post请求的 request body 问题

    http://slackspace.de/articles/log-request-body-with-spring-boot/ (filter + RequestWrapper:最优雅的写法) ht ...