Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
FinSH
RT-Thread "骚操作"之编写优雅的命令行程序
发布于 2019-08-01 01:00:11 浏览:2974
订阅该版
* 本帖最后由 liu2guang 于 2019-8-1 01:01 编辑 * **1. RT-Thread Finsh组件** Finsh 是 RT-Thread 的命令行外壳(shell),提供一套供用户在命令行的操作接口,主要用于调试、查看系统信息。在大部分嵌入式系统中,一般开发调试都使用硬件调试器和 printf 日志打印,在有些情况下,这两种方式并不是那么好用。比如对于 RT-Thread 这个多线程系统,我们想知道某个时刻系统中的线程运行状态、手动控制系统状态。如果有一个 shell,就可以输入命令,直接相应的函数执行获得需要的信息,或者控制程序的行为。这无疑会十分方便。 Finsh支持两种模式: 1. C语言解释器模式, 为行文方便称之为c-style; 2. 传统命令行模式,此模式又称为msh(module shell)。C语言表达式解释模式下, finsh能够解析执行大部分C语言的表达式,并使用类似C语言的函数调用方式访问系统中的函数及全局变量,此外它也能够通过命令行方式创建变量。在msh模式下,finsh运行方式类似于dos/bash等传统shell。 **2. 优雅的命令行程序** 在提及如何优雅的编写 RT-Thread 之前我们肯定得知道什么才算优雅的 "命令"。在这里考虑到大家都是使用过 RT-Thread 的程序猿(废话:不用 RT-Thread 谁会看你的教程),我们在这里拿大家每天都在用的一个命令来举例:"pkgs",想必在做的各位都是用过这个命令的吧(台下观众:我就没遇到过,笔者:大门在那边大爷你慢走不送(*^_^*)),这里我给大家看一张图:![1.png](/uploads/201908/01/005702cj48qqj48eb6rrxj.png) 我们来分析下这张图,我们输入一个 "pkgs" 的命令后,在 RT-Thread 的 ENV 软件中出现了以下打印: > usage: env.py package [-h] [--force-update] [--update] [--list] [--wizard] [--upgrade] [--printenv] optional arguments: -h, --help show this help message and exit --force-update force update and clean packages, install or remove the packages by your settings in menuconfig --update update packages, install or remove the packages by your settings in menuconfig --list list target packages --wizard create a new package with wizard --upgrade upgrade local packages list and ENV scripts from git repo --printenv print environmental variables to check 由上面的打印我们可以大致猜到 “pkgs” 有7个命令,同时观察后面的选项参数备注我们就能知道这个命令是什么意图:例如 “pkgs --list” 我们可以可以猜出是列出当前 BSP 下所有使用的软件包。 不知道大家看到这里对 “优雅” 的命令有没有直观的感受了,除此之外我们还可以看到很多类似的命令,git / ping / vi / vim 等等。其实笔者在这里告诉大家其实这些命令都是有规范的,很多 linux 命令行程序都是遵循这套逻辑。所以呢,我们设想一下我们如果在 RT-Thread 都编写这些标准的命令行程序岂不是以后 RT-Thread 的 Finsh 有大家想要的各种工具了呢,关键是可能不需要文档和教程就可以轻松使用。 所以关键问题来了,我们能不能在 RT-Thread 上编写这种命令行程序呢?答案当然是能的拉~,笔者可以告诉大家除了可以编写自己的,还可以按照这套方法去移植 linux 的程序~ **3. GNU命令标准** 在给大家科普方法之前,我们得先了解下命令行的标准:大家可以看到下图: ![2.png](/uploads/201908/01/005702l2lx7x7xynxu5b78.png) 图中展示了一个完整的GUN命令,一个完整的GUN命令主要由4部分组成: * 命令名(Executable): 命令行程序名称。 * 子命令(Command): 命令行程序子功能名称。 * 选项(Options): 子命令功能的配置选项。 * 参数(Arguments):子命令功能的配置选项对应参数。 我们可以看到图中这行命令:git reset --hard 19b48c8ab34607712d2e3fad7db82f18f1fa5096,这行命令主要将 git 记录回退到 19b48c... 这个commit。 git 是 命令名,reset是这个命令下的一个子命令,这个命令我们使用了 --hard 的选项,这个选项跟了一个SHA1值的参数。 除此之外选项也有很多不同的种类。 第一我们可以将选项按照长短分为了2大类: * 短选项: 由一个中横线+单字母组成,例如:pkgs -h 中的 -h 选项 * 长选项: 由两个中横线+单词或者字母组成,例如:scons --taget=mdk5 中的 --target 选项 第二我们可以将选项按照是否带有参数分为3大类: * 不能带参数:选项后面一定不能带参数 * 必须带参数:选项后面必须带参数 * 参数可选:选项后面的参数可选 对于命令行的参数一般有几种方式: * wavplay -v 50 * wavplay -v50 * wavplay --vol=50 到此为止基本就可以大家科普完毕。 **4. 使用 optparse 仿造 pkgs 实现命令行** 首先我们先按照 optparse 软件包的要求将命令行的命令定义好: MSH_CMD_EXPORT_ALIAS(pkgs, pkgs, this is test cmd.); 这行代码对于导出过 finsh 命令的人肯定不陌生,这里笔者就不在这里细讲了,有兴趣但是没遇到过的请在 RT-Thread 文档中心学习哈。 命令名搞好了,由于pkgs没有子命令,这个我们先忽略,接下来就是pkgs的各种选项了,所以我们接着来实现选项,观察 pkgs 发现有长命令也有短命令,我们按照 optparse 的要求实现选项的结构体, > static struct optparse_long long_opts[] = { {"help" , 'h', OPTPARSE_NONE}, // 长命令: help, 对应短命令 h, 不带参数 {"force-update", 0 , OPTPARSE_NONE}, // 长命令: force-update, 不带参数 {"update" , 0 , OPTPARSE_NONE}, {"list" , 0 , OPTPARSE_NONE}, {"wizard" , 0 , OPTPARSE_NONE}, {"upgrade" , 0 , OPTPARSE_NONE}, {"printenv" , 0 , OPTPARSE_NONE}, { NULL , 0 , OPTPARSE_NONE} }; 选项编辑完了后,我们需要需要编写命令和每一个选项及其参数的使用说明: > static void usage(void) { rt_kprintf("usage: env.py package [-h] [--force-update] [--update] [--list] [--wizard]
"); rt_kprintf(" [--upgrade] [--printenv]
"); rt_kprintf("optional arguments:
"); rt_kprintf(" -h, --help show this help message and exit
"); rt_kprintf(" --force-update force update and clean packages, install or remove the
"); rt_kprintf(" packages by your settings in menuconfig
"); rt_kprintf(" --update update packages, install or remove the packages by your
"); rt_kprintf(" settings in menuconfig
"); rt_kprintf(" --list list target packages
"); rt_kprintf(" --wizard create a new package with wizard
"); rt_kprintf(" --upgrade upgrade local packages list and ENV scripts from git repo
"); rt_kprintf(" --printenv print environmental variables to check
"); } 解析来是解析了,当然我们不可能实现功能,但是解析的代码框架都是一样的,所以这里我们贴出这个架子: > int pkgs(int argc, char **argv) { int ch; int option_index; struct optparse options; if(argc == 1) { usage(); return RT_EOK; } optparse_init(&options, argv); while((ch = optparse_long(&options, long_opts, &option_index)) != -1) { ch = ch; rt_kprintf("
"); rt_kprintf("optopt = %c
", options.optopt); rt_kprintf("optarg = %s
", options.optarg); rt_kprintf("optind = %d
", options.optind); rt_kprintf("option_index = %d
", option_index); } rt_kprintf("
"); return RT_EOK; } 当前最后不能缺了使用的函数的头文件: > #include "optparse.h" #include "finsh.h" 到这里我们就实现完毕了,可以上去编译下载,看效果了,这里是笔者的效果: ![3.png](/uploads/201908/01/005702ppq506l9bbmea2bs.png) 我们在msh中输入pkgs可以看到打印命令相关信息,我们输入pkgs --list可以看到识别到参数为 空NULL,识别到的选项为编号3,正好是命令定义顺序的结构体索引,我们就依靠 optopt, optarg optind option_index来解析输出的命令。 **5. 在 RT-Thread 添加 optparse 软件包** 在bsp中按照下图开启配置和软件包: ![4.png](/uploads/201908/01/005702taiyayaamyxz8wia.png) ![5.png](/uploads/201908/01/005702lck4kxk13scj1kkz.png) 开启后 pkgs --update 再使用 scons --target=mdk5 -s 生成工程,任何编译下载就可以看到你编写的命令了。 **6. 为什么不使用 linux中的 getopt 解析 GNU 命令行** 有大神看到这里,不仅有个疑问,在linux中编写命令行一般使用的 getopt 进行编写的,为什么 RT-Thread 不使用这个来编写呢? 其实POSIX getopt解析器有三个致命的缺陷. 这些缺陷都是可以通过optparse来解决的: * getopt的解析状态存储在全局变量中, 其中一些变量是静态不可访问的. 这意味着在RTT(RT-Thread)下只能有一个msh命令可以使用getopt, 其他msh命令再次使用getopt会出现bug. 而optparse将每次解析的状态存储在一个属于msh的私有结构体中, 每一个optparse命令解析器不会相互影响. * 在RTT中BSP一般支持Keil, IAR和GCC, 对于keil环境下是无法支持原生的POSIX getopt解析器, 这样就造成了在RTT下实现命令解析是无法移植到Keil中使用的, 代码的兼容性降低! * 在getopt中, 错误消息被打印到stderr. 使用opterr可以禁用此功能, 但消息本身仍然不可访问. optparse通过将错误消息写入它的errmsg字段来解决这个问题, 该字段可以被打印到任何地方. optparse的缺点是, 这个错误消息总是用英语而不是当前语言环境. 所以我们选择 github 上的一个开源库正好解决我们以上的问题。 **7. 如何将 linux 上 getopt 编写的命令移植到 RT-Thread 中** 在这里笔者就不多BB了,直接上图,相信大神看到下面两个图就明白如何移植了: 图1, linux程序: ![6.png](/uploads/201908/01/005702ugsfd66ondbxsnig.png) 图2,RT-Thread 程序: ![7.png](/uploads/201908/01/005702cvf8gz0lvlif0f5f.png) 是不是很简单啊~,(咳嗽, 笔者肚子中的墨水已经干了,没得货了),嗯,就这样,我们下期再见~~~ 对了,最后给大家献上文章中使用代码: ![pkgs.c](/uploads/201908/01/005847vrfj5h8rxqpirhcr.attach)
查看更多
8
个回答
默认排序
按发布时间排序
liu2guang
认证专家
2019-08-01
这家伙很懒,什么也没写!
希望帖子能对大家有所帮助,欢迎顶贴:lol:victory:
来一颗糖
2019-08-01
这家伙很懒,什么也没写!
:victory: 前排
Dryad
2019-08-01
这家伙很懒,什么也没写!
正好需要这么一样东西
shadowliang
2019-08-01
Hello,world!!!
楼主666,学习学习了.
XQQ
2019-08-01
这家伙很懒,什么也没写!
楼主朝网红之路又迈出了一大步
armink
2019-08-01
这家伙很懒,什么也没写!
实用的软件包
bernard
2019-08-01
这家伙很懒,什么也没写!
光光的>骚操作 --- :victory:
Dryad
2019-09-11
这家伙很懒,什么也没写!
长命令参数选项为OPTPARSE_REQUIRED时,无法正确解析 ``` if (option[1]) options->optarg = option + 1; else options->optarg = 0; ``` 从这里看,命令和参数中间不能有空格
撰写答案
登录
注册新账号
关注者
0
被浏览
3k
关于作者
liu2guang
这家伙很懒,什么也没写!
提问
14
回答
100
被采纳
4
关注TA
发私信
相关问题
1
RT-THREAD shell无反应呢?
2
RT-thread2.0beta下用类似linux风格MSH,参数如何输入和导出
3
rt-thread finsh windows下的那个终端软件叫什么来着
4
板子上只有485接口,能把FINSH改造成485的么?
5
finsh最大字符问题
6
finsh命令个数是不是有限制啊
7
finsh支持转义字符吗
8
不用finsh如何知道堆栈使用量
9
强烈建议 RT-Thread下finsh原理深入分析
10
finsh输入命令全部返回null node
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
最新文章
1
freemodbus主机在freertos的适配,参考rtthread例程
2
开源共生 商业共赢 | RT-Thread 2024开发者大会议程正式发布!
3
【24嵌入式设计大赛】基于RT-Thread星火一号的智慧家居系统
4
RT-Thread EtherKit开源以太网硬件正式发布
5
还在担心bsp不好维护吗?快使用yml管理主线bsp
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
rt_mq_消息队列_msg_queue
keil_MDK
ulog
MicroPython
C++_cpp
本月问答贡献
a1012112796
20
个答案
3
次被采纳
张世争
12
个答案
3
次被采纳
踩姑娘的小蘑菇
7
个答案
3
次被采纳
用户名由3_15位
14
个答案
2
次被采纳
rv666
9
个答案
2
次被采纳
本月文章贡献
程序员阿伟
9
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
RTT_逍遥
1
篇文章
8
次点赞
大龄码农
1
篇文章
5
次点赞
ThinkCode
1
篇文章
1
次点赞
回到
顶部
发布
问题
分享
好友
手机
浏览
扫码手机浏览
投诉
建议
回到
底部