这段时间一直在做测试的工程(不是测试的工作),为了应付不同的测试场景,代码使用了解释器风格,至于实现,则使用了多年前写的命令终端代码。那会刚毕业不久,写的代码还是有提升空间。现在重新拾起,打破一般认知中的看不懂6个月前写的代码的刻板印象。

存在问题

原来的工程使用C代码编写,并不严格区分测试代码和实现代码,其中最大的问题是将命令列表做成全局变量并依赖于外部的定义,这样耦合程序非常高。因此需要分离出来。

原工程的文件命名也不太好,如common.h这样的文件,在与其它工程整合时容易冲突,此次一并修改了。

解耦

依赖定义全局的命令列表,但只是指针,添加注册命令接口,由外部使用者调用。将默认的帮助命令调整至内部实现,外部直接使用。代码如下:

static cmd_tbl_t* cmd_table;
static int cmd_table_len = 0;/*register command
*/
void register_command(cmd_tbl_t* table, int len)
{cmd_table = table;cmd_table_len = len;
}int do_help_default(int argc, char* argv[])
{_do_help_default(cmd_table, argc, argv);return 0;
}

在原有测试代码基础上,添加初始化命令函数,如下:


/* 定义命令列表 */
cmd_tbl_t my_cmd_table[] =
{// do_help_default为默认函数,可重新实现{"help", CONFIG_SYS_MAXARGS, do_help_default, "print help info."},{"print", 2, do_print, "print the env."},{"exit", 1, do_exit, "exit..."},{"quit", 1, do_exit, "exit..."},
};// 初始化,注册命令
void cmd_init()
{int len = sizeof(my_cmd_table) / sizeof(my_cmd_table[0]);register_command(my_cmd_table, len);
}

这样,在主体函数开始处调用cmd_init();即可。如此一来,结构清晰,逻辑也清晰。

历史命令优化

命令终端支持历史命令,由HIST_MAX决定数量。默认为 10 个。历史命令使用hist_list存储,添加命令函数如下:

static void cread_add_to_hist(char *line)
{strcpy(hist_list[hist_add_idx], line);if (++hist_add_idx >= HIST_MAX)hist_add_idx = 0;if (hist_add_idx > hist_max)hist_max = hist_add_idx;hist_num++;
}

实现很简单,先添加(因为索引从0开始),再累加并与最大值比较。达到最大值后,替换存储的第0个命令。在实际执行中,有时会出现上下命令相同的情况,此时无须再次保存,以节省空间。

修改后代码:

static void cread_add_to_hist(char *line)
{// 判断是否为相同的命令(第0条命令没有相同的说法)if (hist_add_idx > 1 && !strcmp(hist_list[hist_add_idx-1], line)) return;strcpy(hist_list[hist_add_idx], line);if (++hist_add_idx >= HIST_MAX)hist_add_idx = 0;if (hist_add_idx > hist_max)hist_max = hist_add_idx;hist_num++;
}

非命令行模式

截至目前,“命令终端”只有命令行模式,即执行程序初始化后,只会进入命令提示符界面,等待用户输入命令,再解析、执行。有时候,某些场景需要直接执行命令,即自动执行用户输入的命令,亦即将用户命令作为程序的参数。比如,将:

NotAShell> print abc
NotAShell> print 100 200

改为

./a_all.out "print abc; print 100 200;"

的形式,直接执行一次程序即可得到结果,不用手工输入命令。

实现源码:

// 去掉前后空格——中间的不去掉
std::string& trim(std::string &str)
{if (str.empty()){return str;}str.erase(0, str.find_first_not_of(" ")); //去除左边空格str.erase(str.find_last_not_of(" ") + 1);//去除右边空格return str;
}// 分割string,能自动去掉分隔符前后的空格
std::vector <std::string> splitString(const std::string & s, const std::string & delim)
{std::vector <std::string> elems;std::string tmp;size_t pos = 0;size_t len = s.length();size_t delim_len = delim.length();if (delim_len == 0)return elems;while (pos < len){int find_pos = s.find(delim, pos);if (find_pos < 0){tmp = s.substr(pos, len - pos);elems.push_back(trim(tmp));break;}tmp = s.substr(pos, find_pos - pos);elems.push_back(trim(tmp));pos = find_pos + delim_len;}return elems;
}//*
本函数功能:
用于测试组装命令的场景。
使用如下:
./a_all.out "print abc; print 100 200;"
即只有一个参数。为了复用已有命令终端,将参数分析还原为argc argv形式,再调用
*/int readline_cmd_allone(int argc, char ** argv)
{if (argc < 2) return -1;// 命令参数最大为10个
#define MAX_ARGC 10std::vector<std::string> v = splitString(argv[1], ";");cmd_tbl_t *ptable = NULL;char* myargv[MAX_ARGC] = {NULL};int myargc = 0;for(unsigned int i=0; i<v.size(); i++){// printf("split: [%s] %d\n", v[i].c_str(), v[i].empty());if(v[i].empty()){continue;}char cmd[128];memcpy(cmd, v[i].c_str(), 128);// 是否再转成argv的形式??std::vector<std::string> vv = splitString(cmd, " ");myargc = (int)vv.size();myargc = myargc > MAX_ARGC ? MAX_ARGC : myargc;for (int j = 0; j < myargc; j++){myargv[j] = (char*)vv[j].c_str();}// for (int j = 0; j < myargc; j++)// {//     printf("myargv[%d]: %s\n", j, myargv[j]);// }ptable = find_table(myargv[0]);if (ptable == NULL){printf("cmd name: [%s] not found\n", myargv[0]);continue;}printf("name: %s\n", ptable->name);ptable->cmd(myargc, myargv);}return 0;
}

主函数变更如下:


int main(int argc, char* argv[])
{char* p;char* cmdname = *argv;if ((p = strrchr (cmdname, '/')) != NULL){cmdname = p + 1;}if (strcmp(cmdname, "a.out") == 0){if (readline_cmd(argc, argv) != 0)return -1;return 0;}else if (strcmp(cmdname, "a_all.out") == 0){if (readline_cmd_allone(argc, argv) != 0)return -1;return 0;}return 0;
}

代码以a.outa_all.out为执行文件名称作为示例。不管哪种形式,都可以直接复用已有的模块。笔者实际使用的场景,是一个用于自测的程序,有时,需要手动修改参数进行测试,有时需要将程序放到后台执行(因为耗时较长)。

“命令终端”的实现4-优化之解耦相关推荐

  1. Debian11镜像更新为阿里巴巴开源镜像站镜像,切换root用户,解决用户名不在sudoers文件中此事将被报告,Debian11 文件夹对话框、火狐浏览器、命令终端等没有最大化和最小化

    选择Debian作为编程开发最佳Linux的理由: Debian是面向程序员的最古老,最出色的Linux发行版之一.Debian提供了具有.deb软件包管理兼容性的超稳定发行版.Debian为程序员提 ...

  2. Golang实践录:命令行cobra库实例优化

    本文上一文章<Golang实践录:命令行cobra库实例> 的优化,主要的子命令的业务实现的整理. 起因 旧版本中,每个子命令的入口函数,均需一一判断传入参数,并调用对应的业务实现函数,编 ...

  3. Ubuntu命令终端查看使用过的命令

    使用history命令 cyf@ubuntu:~$ history 但是这样会显示出所有使用过的命令,可以在history后加上less cyf@ubuntu:~$ history | less 会显 ...

  4. Win7命令终端基础配色指南

    微软对控制台字体的元数据有严格的限制,https://support.microsoft.com/zh-cn/help/247815/necessary-criteria-for-fonts-to-b ...

  5. Windows 如何在命令终端(CMD)使用命令来访问本地/远程的 Oracle 数据库呢?

    打开命令窗口后直接输入 sqlplus sys/123@orcl as sysdba 其中 sys 是用户名,123 是密码,orcl 是数据库实例名,as sysdba 表示用户 sys 是数据库管 ...

  6. Windows 如何用命令终端(CMD)启动和停止 MySQL 数据库服务

    当安装完 MySql 后,默认每次 Windows 启动的时候都会将 MySql 服务启动起来.那么如何通过命令方式来启动和停止 MySQL 服务呢? Windows XP 如果是 Windows X ...

  7. Linux 的命令终端(CMD)的快捷键(Keyboard of MacBook)

    文章目录 常用 移动光标 编辑命令 查找历史命令 控制命令 命令终端界面滚屏 命令终端页签切换 奇葩 常用 快捷键 说明 Ctrl + A 光标跳到本行的行首 Ctrl + E 光标跳到本行的行尾 C ...

  8. Windows 命令终端(CMD)的快捷键

    这些快捷键只在Windows系统操作有效,连接远程Linux主机,再操作这些快捷键是无效的,因为连接远程的Linux主机后,你用的是Linux命令终端. 快捷键: F1:按F1逐字显示最后一次执行的命 ...

  9. 如何通过命令终端访问本地/局域网/远程的MySQL数据库_访问数据库_连接数据库_登录数据库

    文章目录 Windows系统下 访问本地MySQL数据库 访问远程主机的MySQL数据库 本地安装了MySQL数据库 本地没有安装MySQL Linux系统下 退出数据库登录 Windows系统下 访 ...

最新文章

  1. 中国电子学会青少年编程能力等级测试图形化三级编程题:海底寻宝
  2. 添加javascript代码:_JavaScript的使用
  3. 什么是mysql分发版_MySQL:使用源码分发版还是二进制分发版
  4. 大话PM|产品设计中常被忽视的业务异常
  5. python爬虫与django_请问django和爬虫程序如何整合?
  6. 沈伟华:图神经网络的三连问
  7. centos7 安装nginx报错./configure: error: the HTTP rewrite module requires the PCRE library
  8. Qt linux双屏,qt5 多屏显示
  9. python2.7初学(〇)
  10. node.js(二)创建服务器
  11. Android Contentprovider的学习实例总结
  12. 一个简单的makefile编写(gcc)
  13. 如何恢复删除的文件?4种常用方法教你恢复被删除的文件
  14. 谈谈自我介绍与第一印象
  15. dw网页制作的基本步骤_网页制作一般使用哪些工具?DW使用方法教学?
  16. hexo(sakura)给博客增添侧边栏(回到顶部,跳转评论,深色模式,播放音乐)Valine-1.4.4新版本尝鲜+个性制定(表情包、qq头像、UI样式)
  17. 软件测试|测试金字塔是什么,它的目的是什么,以及它包含哪些层次?
  18. 字体颜色 * 博客 * 好看
  19. html实现放大镜效果,利用jquery实现放大镜特效
  20. 简述直方图和柱形图的区别_什么是直方图?跟柱状图有什么区别?终于有人讲明白了...

热门文章

  1. python常用内置方法_Python3 常用的几个内置方法
  2. JavaScript鼠标经过图片加亮显示
  3. 在周二正式发售前 新款MacBook Pro已开始向客户发货
  4. 华为Mate 50系列明年亮相:或首发高通骁龙898 4G芯片
  5. Apple Watch移动心电图ECG与房颤提示功能获药监局审核
  6. 已完成私有化交易 “网红第一股”如涵退市
  7. 年初至今主动权益基金平均亏损2.96%
  8. 蚂蚁集团前三季度营收1181.91亿元 支付宝月活用户7.31亿
  9. iPhone 12无线充电模块曝光:AirPower有戏了!
  10. iPhone 12系列屏幕细节曝光:三星OLED屏独霸大半