RT-Thread "骚操作"之编写优雅的命令行程序

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”,想必在做的各位都是用过这个命令的吧(台下观众:我就没遇到过,笔者:大门在那边大爷你慢走不送(^_^)),这里我给大家看一张图:

我们来分析下这张图,我们输入一个 “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命令标准

在给大家科普方法之前,我们得先了解下命令行的标准:大家可以看到下图:

图中展示了一个完整的GUN命令,一个完整的GUN命令主要由4部分组成:

  1. 命令名(Executable): 命令行程序名称。

  2. 子命令(Command): 命令行程序子功能名称。

  3. 选项(Options): 子命令功能的配置选项。

  4. 参数(Arguments):子命令功能的配置选项对应参数。

我们可以看到图中这行命令:git reset --hard 19b48c8ab34607712d2e3fad7db82f18f1fa5096,这行命令主要将 git 记录回退到 19b48c… 这个commit。

git 是 命令名,reset是这个命令下的一个子命令,这个命令我们使用了 —hard 的选项,这个选项跟了一个SHA1值的参数。

除此之外选项也有很多不同的种类。

第一我们可以将选项按照长短分为了2大类:

  1. 短选项: 由一个中横线+单字母组成,例如:pkgs -h 中的 -h 选项

  2. 长选项:由两个中横线+单词或者字母组成,例如:scons- -taget=mdk5 中的 --target 选项

第二我们可以将选项按照是否带有参数分为3大类:

  1. 不能带参数:选项后面一定不能带参数

  2. 必须带参数:选项后面必须带参数

  3. 参数可选:选项后面的参数可选

对于命令行的参数一般有几种方式:

  1. wavplay -v 50

  2. wavplay -v50

  3. 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]\n");
    rt_kprintf("                      [--upgrade] [--printenv]\n\n");
    rt_kprintf("optional arguments:\n");
    rt_kprintf("  -h, --help      show this help message and exit\n");
    rt_kprintf("  --force-update  force update and clean packages, install or remove the\n");
    rt_kprintf("                  packages by your settings in menuconfig\n");
    rt_kprintf("  --update        update packages, install or remove the packages by your\n");
    rt_kprintf("                  settings in menuconfig\n");
    rt_kprintf("  --list          list target packages\n");
    rt_kprintf("  --wizard        create a new package with wizard\n");
    rt_kprintf("  --upgrade       upgrade local packages list and ENV scripts from git repo\n");
    rt_kprintf("  --printenv      print environmental variables to check\n");
}

解析来是解析了,当然我们不可能实现功能,但是解析的代码框架都是一样的,所以这里我们贴出这个架子:

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("\n");
        rt_kprintf("optopt = %c\n", options.optopt);
        rt_kprintf("optarg = %s\n", options.optarg);
        rt_kprintf("optind = %d\n", options.optind);
        rt_kprintf("option_index = %d\n", option_index);
    }
    rt_kprintf("\n");

    return RT_EOK;
}

当前最后不能缺了使用的函数的头文件:

#include "optparse.h"
#include "finsh.h"

到这里我们就实现完毕了,可以上去编译下载,看效果了,这里是笔者的效果:

我们在msh中输入pkgs可以看到打印命令相关信息,我们输入pkgs —list可以看到识别到参数为 空NULL,识别到的选项为编号3,正好是命令定义顺序的结构体索引,我们就依靠 optopt, optarg optind option_index来解析输出的命令。

5. 在 RT-Thread 添加 optparse 软件包

在bsp中按照下图开启配置和软件包:

开启后 pkgs --update 再使用 scons --target=mdk5 -s 生成工程,任何编译下载就可以看到你编写的命令了。

6. 为什么不使用 linux中的 getopt 解析 GNU 命令行

有大神看到这里,不仅有个疑问,在linux中编写命令行一般使用的 getopt 进行编写的,为什么 RT-Thread 不使用这个来编写呢?

其实POSIX getopt解析器有三个致命的缺陷. 这些缺陷都是可以通过optparse来解决的:

  1. getopt的解析状态存储在全局变量中,  其中一些变量是静态不可访问的.这意味着在RTT(RT-Thread)下只能有一个msh命令可以使用getopt,  其他msh命令再次使用getopt会出现bug. 而optparse将每次解析的状态存储在一个属于msh的私有结构体中,  每一个optparse命令解析器不会相互影响.

  2. 在RTT中BSP一般支持Keil, IAR和GCC, 对于keil环境下是无法支持原生的POSIX getopt解析器, 这样就造成了在RTT下实现命令解析是无法移植到Keil中使用的, 代码的兼容性降低!

  3. 在getopt中,  错误消息被打印到stderr. 使用opterr可以禁用此功能, 但消息本身仍然不可访问.  optparse通过将错误消息写入它的errmsg字段来解决这个问题, 该字段可以被打印到任何地方. optparse的缺点是,  这个错误消息总是用英语而不是当前语言环境.

所以我们选择 github 上的一个开源库正好解决我们以上的问题。

7. 如何将 linux 上 getopt 编写的命令移植到 RT-Thread 中

在这里笔者就不多BB了,直接上图,相信大神看到下面两个图就明白如何移植了:

图1, linux程序:

图2,RT-Thread 程序:

是不是很简单啊~,(咳嗽, 笔者肚子中的墨水已经干了,没得货了),嗯,就这样,我们下期再见~~~

文章中使用代码,可通过阅读原文帖子中获取。

 

RT-Thread线上活动

1、【RT-Thread能力认证考试12月——RCEA】经过第一次考试的验证,RT-Thread能力认证得到了更多社区开发者和产业界的大力支持(点此查看)如果您有晋升、求职、寻找更好机会的需要,有深入学习和掌握RT-Thread的需求,欢迎垂询/报考!

能力认证官网链接:https://www.rt-thread.org/page/rac.html(在外部浏览器打开)

立即报名

#题外话# 喜欢RT-Thread不要忘了在GitHub上留下你的STAR哦,你的star对我们来说非常重要!链接地址:https://github.com/RT-Thread/rt-thread

你可以添加微信18917005679为好友,注明:公司+姓名,拉进 RT-Thread 官方微信交流群

RT-Thread


让物联网终端的开发变得简单、快速,芯片的价值得到最大化发挥。Apache2.0协议,可免费在商业产品中使用,不需要公布源码,无潜在商业风险。

长按二维码,关注我们

看这里,求赞!求转发!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值