[原创] 提供一个开源模块MiniShellEx

lzwml   2016-6-12 20:29 楼主
提供一个开源模块MiniShellEx 下载地址 https://github.com/MenglongWu/MiniShellEx 目前的版本是V1.0-rc1 MiniShellEx是一个自定义命令解析模块,除了直接从键盘读取输入执行命令外,还提供其他方式执行命令(下面会解释,使用方法需要想象力)。 主要应用: 1. 该工程主要用于以Linux为平台的嵌入式设备,提供一种以命令行控制程序运行的方法,通常用于设备远程操控。 2. 有好的自动补全与命令层次关系,免去记忆麻烦。如果有过Cisco、华为等带网管功能的设备使用经验这一点不难理解,我在工程的Demo里有一个仿交换机控制台的工程 3. 分析配置脚本,并执行脚本内容。 我在之前的项目用该模块调试与开发机房机架监控设备,随着项目的进行,本来的调试手段成了产品的后门。 本地与嵌入式端通过网络连接(TCP/IP、串口、USB),本地终端下输入命令,将命令以字符串形式发送到嵌入式设备,嵌入式设备执行后再反馈回来。 工程由来: 懒惰是再创造的火种。 1. 最初使用uboot的命令行调试程序,以及用uboot做裸机系统,对于小系统来说还算凑合,随着项目庞大后uboot的命令行越显力不从心。特别是当命令长度超过4个,系统可用命令有数十个之多时,有好的命令提示与自动补全尤其有必要(uboot其实自带命令补全,不过发现时我已经用MiniShellEx实现,也就没继续深究)。 2. 命令太多时当我忘记命令了,查阅help时会将数十个命令帮助都显示,满屏的帮助不利于查阅,我希望有个过滤功能。 3. help命令之你只能提供命令名的提示,而无法提供参数提示。借鉴Cisco的用户体验,模仿它做了参数提示。这一点还是相当有用的,有时候项目开发数个月,有几个命令不那么常用,下次使用时记不起参数(我也不可能写个man page),通常都是查阅源代码,或者打印一张纸上写下命令调用方法。 工程采用readline库作为依赖,要实现上面的功能(自动补全、命令历史)都不是上面难事。 使用举例: 1. 控制台或网络方式传递命令: cmd1 argv1 argv2 argv3 想必不用多解释,与uboot一样cmd1是查询命令的简称,查找命令树里满足与cmd1匹配的命令则被调用。 int do_cmd1(void *ptr, int argc, char **argv); 2. 命令分组 输入?键提示当前可用命令的提升。当命令过多时特别占用屏幕。 cmd1 help 1 cmd2 help 2 ... cmdn help n 采用Cisco的分组方式后,命令被分开了,当命令个数在15个以内看起来比较舒服(帮助显示能保持在一屏以内)。 uboot里所有命令都集中在一个数组内,而MiniShellEx可以放在若干个数组里,查看帮助时只显示该数组里的内容,执行也仅限于该数组内的命令,两数组命令名简称相同并不会冲突,所以给命令起名字也不必加前缀。 当然分组也是有依据的,通常有个命令组作为根,其他命令组是根命令组的详细子集,或是其他命令组的子集,子集的层数可在源码里限定,默认拥有16(PROMPT_DEPTH)层子集 如根命令boot_root有如下命令 nand sd ip print 对于uboot的情况,如果要执行nand操作有下面指令 MiniShell>nand erase 0x100000 0x200000 MiniShell>nand write 0x20000000 0x100000 0x200000 MiniShell>nand read 0x20000000 0x100000 0x200000 而对于MiniShellEx你可以将命令分组,nand命令的第一个参数作为子集成员,也是每个命令的简称 boot_nand命令组有如下命令 erase write read quit 此时按下?键查询帮助,查询道德是boot_nand组,其中quit命令用于回退到boot_root命令组,此时该命令组各命令的有效参数应该如下 MiniShell/nand >erase 0x100000 0x200000 MiniShell/nand >write 0x20000000 0x100000 0x200000 MiniShell/nand >read 0x20000000 0x100000 0x200000 上面的命令输入比uboot方式少输入几个字符,并且在输入命令忘记参数后,按?键对对后面的参数做提示 MiniShell/nand >erase addr 擦除起始地址 all 擦除全部 MiniShell/nand >erase 0x100000 size 擦除大小 MiniShell/nand >erase 0x100000 0x200000 按回车,命令结束 加入sd命令也有一套与nand相类似的子集,上面说过,它们命令简称相同并不会引起冲突。 boot_sd命令组有如下命令 erase write read quit 3. 读取并执行脚本 假如存在脚本 --------------------- # rewrite nand [nand] erase 0x100000 0x200000 write 0x20000000 0x100000 0x200000 # set network [net] ip = 192.168.1.3 gw = 192.168.1.1 mask = 255.255.255.0 --------------------- MiniShellEx默认分析字符串的方法是将字符串中的" ,\t\n"几种字符作为参数分割符,为解析上面的脚本需要自定义分割字符" ,\t\n[]=",即扩展"[]="三个字符。 将脚本文件“以单行读取,每行是一个命令”,分割后MiniShellEx看到的字符串应该是这样。 --------------------- # rewrite nand nand erase 0x100000 0x200000 write 0x20000000 0x100000 0x200000 # set network net ip 192.168.1.3 gw 192.168.1.1 mask 255.255.255.0 --------------------- 其中第5行是空行,找不到任何命令所以不会执行; 第1、6行命令简称是 “#” 没有命令,所以不会被执行 我的MiniShellEx模块里有个msbuild工具,用于生产各组命令,生成它们的层次关系,尤其是命令参数提示,不然手动去编写这些数据结构相当累人。他需要编辑xml文件,本工程xml文件规则理应不该让用户去背、去手动编辑,后期会再做一个工具msedit由它去生成xml文件,同时做配置差错处理,例如同一命令组内有命令简称冲突,两命令回调函数冲突,命令组命名冲突等,并提示用户修改。msedit采用ncurses库。 本帖最后由 lzwml 于 2016-6-12 20:31 编辑

回复评论 (3)

未命名.JPG
点赞  2016-6-13 09:38
哇  谢谢分享  
加油!在电子行业默默贡献自己的力量!:)
点赞  2016-6-13 09:44
  1. #include <stdio.h>
  2. #include <minishell_core.h>
  3. extern struct cmd_prompt boot_root[];
  4. struct user_ptr
  5. {
  6. char *name;
  7. int a;
  8. };
  9. int main(int argc, char **argv)
  10. {
  11. sh_whereboot(boot_root);
  12. struct sh_detach_depth depth;
  13. char *cmd[12];
  14. depth.cmd = cmd;
  15. depth.len = 12;
  16. depth.seps = " \t";
  17. struct user_ptr upt;
  18. upt.name = "This is a minishell_core demo, modeled layer 3 Switch\n";
  19. upt.a = 1001;
  20. sh_enter_ex(&depth, &upt);
  21. return 0;
  22. }
模块在使用msbuild生成层次关系后,main()里只用调用两个函数即可运行。 sh_whereboot告知从哪个命令分组开始作为根命令 sh_enter_ex启动MiniShellEx,等待用户输入。 其中depth是命令分割参数以及最大命令层数, upt是用户传递给MiniShellEx的参数,被调用的命令处理代码do_xxx可以收到该参数 static int do_xxx(void *ptr, int argc, char **argv); 本帖最后由 lzwml 于 2016-6-16 17:28 编辑
点赞  2016-6-16 17:20
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复