# tinySH **Repository Path**: jf_linux/tinySH ## Basic Information - **Project Name**: tinySH - **Description**: tinySH是常用于集成在嵌入式设备软件中,以命令行形式交互的,方便工程师调试设备的命令行工具。 该工具用纯C语言编译,非常方便集成。 - **Primary Language**: C - **License**: GPL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2021-12-07 - **Last Updated**: 2021-12-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README #tinySH ##1. 简述 tinySH是一个用C语言实现,主要集成在嵌入式设备软件中,用于实现通过串口进行命令行交互的接口。我们可以像Linux的终端那像通过执行命令来完成某些操作。 ###1.1 它有什么用? 对于某些基于单片机的程序,我们不能或很难进行单步调试。为了方便调式,我们会开一个串口,接上终端,通过查看设备输出的调试信息,监视程序的运行过程。但是,这些远远不够。比如设备运行异常了,我们很希望能主动地获取某些信息,比如变量的值、队列的使用情况、任务的状态等等。有时,为了判定Bug的范围,我们可能希望直接地操作外围设备。 我们急需有一个与设备进行交互的接口。如果没有这样的工具,我们只能挠头不知所措。 tinySH就是为了解决这个问题! ###1.3 适用性 tinySH主要用于单片机级的嵌入式设备软件。当然,在Linux的应用程序上也可以用,我们也可以用它来观察进程中的某些变量的值,特别是正在运行的进程。 ##2. 原理 tinySH的风格很向Linux的Shell。它的功能是让用户输入命令,通过执行命令来达到调试控制的目的。为了方便管理,命令是以树的方式进行组织的。树是由目录与命令组成的。目录就相当于树的支干,而命令就是树的叶子。一个目录可以同时挂目录与命令。  由上图可见,树是由 *目录结点* 与 *命令结点* 组成。 每个结点都有路径,有相对路径与绝对路径。比如 foo 是命令结点,它的绝对路径为 "/dir-1/foo",它相对于 fun 命令结点的路径为 "../dir-1/foo" tinySH里面有3棵树:bin, sbin, root。 * bin --目录为tinySH自带的树,里面已实现如:ls,cd,tree,help,exit 等常用命令,方便用户操作。我们不需要再去修改它。 最常用的是 cd, ls, exit。它们的功能与Shell相似。cd:改变当前路径;ls:列举当前目录下所有子目录或命令;exit:退出命令交互。 * sbin --目录由用户自定义的树。在该目录下的命令,在任何路径下都可以执行。 * root --目录由用户自定义的树。 ##3. 移植 如下图所示:  tinySH需要我们实现两个函数接口。 由于tinySH需要通过串口或别的什么方式与用户进行交互,所以我们需要通过定义tsh_putchar()与tsh_getchar()来让tinySH知道通过什么方式来打印字符与获取用户的输入。 在单片机上,通常tsh_putchar()就是通过串口输出一个字符。而tsh_getchar()则是等待串口接受一个字符。我们需要定义它们,如下: void tsh_puthchar(char ch) { uart_send_char(ch); } char tsh_getchar() { return uart_receive_char(); } **注意:** tsh_getchar()是的功能即时接收用户输入的每一个字符,而不是如Linux的getchar()那样,必须要等用户按Enter才算输入。 参考:[linux C 让 getchar()不再需要回车](http://blog.sina.com.cn/s/blog_49f9ea930100nyqc.html) 然后在特定的位置调用 ShellEntry() 函数,进入命令交互界面。 ##4. 实现 root 与 sbin 结点 如上面所言,tinySH为用户提供了两个树:root, sbin 由用户自己定义。 我们先了解结点的原理,方面我们理解。 ###结点的实现 在shell.h文件中定义了 `node_struc`t 结构体。每个结点都是 `node_struct`。  **type**域指名了该结点的类型:DIR、FUN。同时也指明了ptr域指针的意义。 如果为DIR,那么ptr指针为 `node_struct*` 数组首地址。如果为FUN,那么ptr指针则是 `int (*) (const char *)` 函数指针。 **name**域为结点显示名称,**passwd**为结点访问密码,**help**为结点帮助信息。 ###4.1 定义root结点  上面定义了 root结点,指定了其类型为`NODE_TYPE_DIR`,并将`ptr`域设定为 `_root_dir_array[]` 数组首地址。 **注意**:`_root_dir_array` 数据组必须以 0 作为终止元素,否则tinySH不知道目录下有多下个子结点。 除了 root 结点需要我们定义以外,还有 sbin 结点。定义方式与 root 完成一样。 ###4.2 在 root 目录下加一个 hello 命令 1. 实现函数  `_func_hello` 可以带一个`const char *`的参数`str`。该参数为用户输入命令时所带的字符串。 2. 定义命令描述结点  定义hello结点,将`type`域设置为`NODE_TYPE_FUN`,并将`ptr`设置为`_func_hello` 的函数地址。 3. 将结点添加到目录数组  **注意**:`hello` 前有 `&` 符号。 ###4.3 在 root 目录下加一个 dir 目录 同样是3步: 1. 定义`node_struct*`数组  2. 定义目录描述结点  定义 dir 结点,并指定 `type` 为 `NODE_TYPE_DIR`,`ptr` 为 `_dir_array` node_strcut* 数组。 3. 将结点添加到root目录数组  ###简化 如果你发现上面的定义好繁琐,我也有同感。为此,我在 shell.h 文件里定义了大量的宏,来简化代码编写过程 ####定义目录 如此定义 root 目录:  这样明显比用原始的方式来得更直接。 如下为 `NODE_DIR_DEF` 宏的定义:  详见 tinySH/shell.h 文件。 ####定义命令 hello可以简化成这样:  如下为 `NODE_FUN_DEF` 宏的定义:  详见 tinySH/shell.h 文件。 ###sbin的特点 挂在 root 结点下的命令,只有进入了该命令所在路径下方可执行。sbin下的命令则不是,不管在什么路径下,都可以执行。 ##3. 命令使用 请将源码下载到Linux本地。 执行如下命令进行编译: $ make distclean $ make 执行示例程序: $ ./demo 如下界面:  ### ls `ls [path=.]` 列举当前目录下所有的目录与命令:  可以看到目录下有:hello命令,count与secret目录。 命令与目录的区别在于:**目录名后面有'/'**。 ### cd `usage: cd [path=.]` 改变当前的路径:  ### exit `usage: exit` 退出命令交互接口。在任何目录都有效。 ### tree `useage: tree [path=.]` 查看当前路径下所有子目录与命令,以树的形式展现:  对于标有"locked"的目录或命令,访问它们是需要密码的。 ### help `usage: help