在C语言程序开发过程中无论是示例应用还是工具程序都少不了要进行命令行解析的操作,如何高效优雅的解析命令行中传入的参数是个重要的能力。
无论是sylixos还是Linux,在命令行输入的命令字符串都会把其中的空格去掉,分割为一个字符串数组,通过int argc 和 char *argv[]
传给应用的入口函数,所以一般应用程序的主函数格式为int main(int argc, char *argv[])
。其中argc为分割后参数个数,argv为参数字符串数组且里面的字符串,顺序和命令行中输入的顺序一致。一个命令行至少有一个有效的字符串也就是命令本身即argv[0]
,所以argc的值也至少为1。
如命令为:“cmd arga argb argc”
则 argc = 4
argv[0] = cmd
argv[1] = arga
argv[2] = argb
argv[3] = argc
把命令行中输入的字符串行转换为argc和argv格式是由操作系统完成的,而如何解析argc和argv中内容的含义则是由应用程序自己处理的。
下面列出来4种命令行参数解析方法,都以串口接收并发送为例子列出源码。4种方法由简单到复杂,适配越来越复杂的命令行参数解析操作。
解析命令行参数最简单的方法就是不解析,也就是设计的程序没有命令参数只有命令。这样的应用功能固定单一,一般只能作为简单的示例程序,程序中的很多配置都是以源码的方式写死的,一旦需要修改参数就需要修改源码并重新编译部署。
/*********************************************************************************************************
**
** 中国软件开源组织
**
** 嵌入式实时操作系统
**
** SylixOS(TM)
**
** Copyright All Rights Reserved
**
**--------------文件信息--------------------------------------------------------------------------------
**
** 文 件 名: uartExample.c
**
** 创 建 人: Hou.JinYu (侯进宇)
**
** 文件创建日期: 2018 年 01 月 11 日
**
** 描 述: 串口示例程序,收到一组数据后将此组数据通过串口返回
*********************************************************************************************************/
#include
/*********************************************************************************************************
宏定义
*********************************************************************************************************/
#define BUF_SIZE (512)
#define DEV_NAME "/dev/ttyS1"
/*********************************************************************************************************
** 函数名称: main
** 功能描述: 程序入口
** 输 入: argc 参数个数
** argv 参数列表
** 输 出: ERROR_CODE
*********************************************************************************************************/
int main(int argc, char *argv[])
{
int iFd;
char pcBuff[BUF_SIZE];
ssize_t sstReadLen;
/*
* 打开串口设备
*/
iFd = open(DEV_NAME, O_RDWR);
if (iFd < 0) {
return (-1);
}
/*
* 设置串口属性
* 波特率为 : 115200
* 硬件选项为: 8位数据位, 一位停止位,无校验
*/
ioctl(iFd, SIO_BAUD_SET, SIO_BAUD_115200);
ioctl(iFd, SIO_HW_OPTS_SET, CREAD | CS8);
/*
* 读取输入串口的数据,并原样发送出去
* 注意,默认情况下,读写都可能会阻塞
*/
while (1) {
sstReadLen = read(iFd, pcBuff, BUF_SIZE);
write(iFd, pcBuff, sstReadLen);
}
/*
* 关闭串口设备
*/
close(iFd);
return (0);
}
/*********************************************************************************************************
END
*********************************************************************************************************/
设计命令参数一个简单的思路就是不同位置的参数代表不同含义。这样设计的一个缺点就是只有末尾的参数才能不输入,前面的参数则必须要输入,即便是默认值。
如下面程序所示,输入的命令必须按照cmd [filename] [baudrate] [option]
的格式,后面的参数可以不输入,此时就会使用程序默认的参数值。
以下都是合法的输入命令
- uart /dev/ttyS1 115200 none //设置了全部参数
- uart /dev/ttyS1 115200 //设置文件名和波特率,校验方式使用默认
- uart /dev/ttyS1 //设置文件,校验方式名和波特率使用默认
- uart // 全部使用默认参数
以下则都是不合法的输入命令
- uart 115200 none
- uart none
- uart 115200 none /dev/ttyS1
/*********************************************************************************************************
**
** 中国软件开源组织
**
** 嵌入式实时操作系统
**
** SylixOS(TM)
**
** Copyright All Rights Reserved
**
**--------------文件信息--------------------------------------------------------------------------------
**
** 文 件 名: uartExample.c
**
** 创 建 人: Hou.JinYu (侯进宇)
**
** 文件创建日期: 2018 年 01 月 11 日
**
** 描 述: 串口示例程序,收到一组数据后将此组数据通过串口返回
** cmd [filename] [baudrate] [option]
*********************************************************************************************************/
#include
#include
#include
/*********************************************************************************************************
宏定义
*********************************************************************************************************/
#define BUF_SIZE (512)
#define CFG_DEFAULT_UART_NAME ("/dev/ttyS1")
#define CFG_DEFAULT_UART_BAUD (SIO_BAUD_115200)
#define CFG_DEFAULT_UART_OPTS (CREAD | CS8)
/*********************************************************************************************************
** 函数名称: main
** 功能描述: 程序入口
** 输 入: argc 参数个数
** argv 参数列表
** 输 出: ERROR_CODE
*********************************************************************************************************/
int main(int argc, char *argv[])
{
int iFd;
char pcBuff[BUF_SIZE];
ssize_t sstReadLen;
char cFileName[64] = CFG_DEFAULT_UART_NAME; /* 设备文件名 */
uint32_t uiBaud = CFG_DEFAULT_UART_BAUD; /* 波特率 */
uint32_t uiOption = CFG_DEFAULT_UART_OPTS; /* 硬件选项 */
/*
* 解析输入的命令参数
*/
switch (argc) {
case 4:
if (strcmp(argv[3], "none") == 0) {
uiOption = CREAD | CS8;
} else if (strcmp(argv[3], "odd") == 0) {
uiOption = CREAD | CS8 | PARENB | PARODD;
} else if (strcmp(argv[3], "even") == 0) {
uiOption = CREAD | CS8 | PARENB;
}
case 3:
uiBaud = strtoul(argv[2], NULL, 0);
case 2:
memcpy(cFileName, argv[1], strlen(argv[1]));
case 1:
break;
default:
break;
}
/*
* 打开串口设备
*/
iFd = open(cFileName, O_RDWR);
if (iFd < 0) {
return (-1);
}
/*
* 设置串口属性
*/
ioctl(iFd, SIO_BAUD_SET, uiBaud);
ioctl(iFd, SIO_HW_OPTS_SET, uiOption);
/*
* 读取输入串口的数据,并原样发送出去
* 注意,默认情况下,读写都可能会阻塞
*/
while (1) {
sstReadLen = read(iFd, pcBuff, BUF_SIZE);
write(iFd, pcBuff, sstReadLen);
}
/*
* 关闭串口设备
*/
close(iFd);
return (0);
}
/*********************************************************************************************************
END
*********************************************************************************************************/
getopt
函数是POSIX标准下的一个通用命令参数解析函数,getopt_long
是它的加强版可以支持长选项。
上一节使用的参数都是位置相关参数
,参数位置不能随意改变,也不能随意缺失,用起来不是很方便灵活,尤其是参数比较多时,不得不输入长长的一串参数,还得记着位置和个数不能出错。getopt则使用的是选项参数
,通过“-”来标注选项(getopt_long可以使用长选项,通过“–”来标注),选项后面的则是选项参数,当然选项也可以没有选项参数。首先各选项在命令行中的位置可以是任意的,其次使用默认值得选项可以不输入,这样在输入命令时就方便多了。
关于getopt函数的用法,网上有很多教程这里不再细说,其实也比较简单,看看下面的例程,对照一下应该就基本会用了。
#include
int getopt(int argc, char * const argv[],const char *optstring);
extern char *optarg; //存储选项的参数
extern int optind //指向下一个扫描的位置
extern int opterr //是否显示错误信息
extern int optopt; //读取到的字符如果不在opstring(上面例子是"alRtS")中,则存入optopt中
#include
int getopt_long(int argc, char * const argv[],const char*optstring,
const struct option *longopts, int *longindex);
int getopt_long_only(int argc, char * const argv[],const char *optstring,
const struct option *longopts, int *longindex);
/*********************************************************************************************************
**
** 中国软件开源组织
**
** 嵌入式实时操作系统
**
** SylixOS(TM)
**
** Copyright All Rights Reserved
**
**--------------文件信息--------------------------------------------------------------------------------
**
** 文 件 名: uartExample.c
**
** 创 建 人: Hou.JinYu (侯进宇)
**
** 文件创建日期: 2018 年 01 月 11 日
**
** 描 述: 串口示例程序,收到一组数据后将此组数据通过串口返回
** cmd [-f filename]/[-b baudrate]/[-o option]
*********************************************************************************************************/
#include
#include
#include
/*********************************************************************************************************
宏定义
*********************************************************************************************************/
#define BUF_SIZE (512)
#define CFG_DEFAULT_UART_NAME ("/dev/ttyS1")
#define CFG_DEFAULT_UART_BAUD (SIO_BAUD_115200)
#define CFG_DEFAULT_UART_OPTS (CREAD | CS8)
/*********************************************************************************************************
** 函数名称: main
** 功能描述: 程序入口
** 输 入: argc 参数个数
** argv 参数列表
** 输 出: ERROR_CODE
*********************************************************************************************************/
int main(int argc, char *argv[])
{
int c;
int iFd;
char pcBuff[BUF_SIZE];
ssize_t sstReadLen;
char cFileName[64] = CFG_DEFAULT_UART_NAME; /* 设备文件名 */
uint32_t uiBaud = CFG_DEFAULT_UART_BAUD; /* 波特率 */
uint32_t uiOption = CFG_DEFAULT_UART_OPTS; /* 硬件选项 */
/*
* 解析输入的命令参数
*/
while (1) {
c = getopt(argc, argv, "f:b:o:");
if (c == -1) {
break;
}
switch (c) {
case 'f':
memcpy(cFileName, optarg, strlen(optarg));
break;
case 'b':
uiBaud = strtoul(optarg, NULL, 0);
break;
case 'o':
if (strcmp(optarg, "none") == 0) {
uiOption = CREAD | CS8;
} else if (strcmp(optarg, "odd") == 0) {
uiOption = CREAD | CS8 | PARENB | PARODD;
} else if (strcmp(optarg, "even") == 0) {
uiOption = CREAD | CS8 | PARENB;
}
break;
default:
return (PX_ERROR);
}
}
/*
* 打开串口设备
*/
iFd = open(cFileName, O_RDWR);
if (iFd < 0) {
return (-1);
}
/*
* 设置串口属性
*/
ioctl(iFd, SIO_BAUD_SET, uiBaud);
ioctl(iFd, SIO_HW_OPTS_SET, uiOption);
/*
* 读取输入串口的数据,并原样发送出去
* 注意,默认情况下,读写都可能会阻塞
*/
while (1) {
sstReadLen = read(iFd, pcBuff, BUF_SIZE);
write(iFd, pcBuff, sstReadLen);
}
/*
* 关闭串口设备
*/
close(iFd);
return (0);
}
/*********************************************************************************************************
END
*********************************************************************************************************/
虽然getopt函数使用比较广泛,也能完成大部分参数解析工作,但当参数很多很复杂时用着就比较费时费力了。为了简化并高效率进行参数解析,科技猎人设计了cmdParse函数模块,虽然实现不是很复杂,但是真的很好用啊!!!
cmdParse和getopt一样使用选项参数,但不区分短选项和长选项,选项可以是任意字符串,只要和输入完全匹配即可。同时cmdParse也没有选项字串,只有选项列表,是通过选项列表的的目标来工作的,可以是直接解析选项参数并回填参数值,也可以是回调一个函数进行更复杂的选项参数处理。
cmdParse函数模块的实现稍后会在另一篇博客里细讲,这里先列一个使用cmdParse函数的例子。其实初看是体会不到cmdParse函比数getopt函数方便多少的,读者可以试着用getopt函数实现一下同样功能,看看有多大区别。
/*********************************************************************************************************
**
** 中国软件开源组织
**
** 嵌入式实时操作系统
**
** SylixOS(TM) LW : long wing
**
** Copyright All Rights Reserved
**
**--------------文件信息--------------------------------------------------------------------------------
**
** 文 件 名: uartAccessCmd.c
**
** 创 建 人: Hou.JinYu (侯进宇)
**
** 文件创建日期: 2017 年 10 月 17 日
**
** 描 述: 通用串口测试工具
*********************************************************************************************************/
#include
#include
#include
#include
#include
#include "cmdParse/cmdParse.h"
/*********************************************************************************************************
宏定义
*********************************************************************************************************/
#define CFG_DEFAULT_BUF_SIZE (64) /* 默认缓存大小 */
#define CFG_DEFAULT_BUF_COUNT (32) /* 默认缓存个数 */
/*********************************************************************************************************
宏函数
*********************************************************************************************************/
#if 1
#define PRINT_DEBUG(fmt, ...) do {printf(fmt, ##__VA_ARGS__);} while (0)
#else
#define PRINT_DEBUG(fmt, ...)
#endif
#define PRINT_ERROR(fmt, ...) do {printf("[ERROR]%s:%d---", __FILE__, __LINE__); \
printf(fmt, ##__VA_ARGS__); } while (0)
#define PRINT_DATA_UINT8(ptr, len, fmt, ...) \
do {UINT j; \
printf(fmt, ##__VA_ARGS__); \
for (j = 0; j < (len); j++) { \
if ((j & 0x0f) == 0) {printf("\r\n[%04x]", j);} \
printf(" %02x", ((UINT8 *)ptr)[j]); \
} printf("\n");} while (0)
/*********************************************************************************************************
设备配置参数结构
*********************************************************************************************************/
typedef struct {
UINT32 uiEchoState; /* 0 不反射 1反射接收的数据 */
UINT32 uiDisplayState; /* 0 不显示收发数据 1显示 */
INT iFd; /* 设备文件标识符 */
pthread_t tidRecv; /* 接收线程ID */
pthread_t tidEcho; /* 反射线程ID */
pthread_t tidSend; /* 发送线程ID */
LW_OBJECT_HANDLE hQueueSend; /* 消息队列句柄 */
CHAR cFileName[64]; /* 波特率 */
UINT32 uiBaud; /* 波特率 */
UINT32 uiOption; /* 硬件选项 */
UINT32 uiRecvCount; /* 串口接收数据计数 */
UINT32 uiEchoCount; /* 串口反射数据计数 */
UINT32 uiSendCount; /* 串口发送数据计数 */
UINT32 uiSendInterval; /* 发送间隔,单位毫秒 */
UINT32 uiSendNumber; /* 串口发送次数 */
INT32 uiBufLength; /* 缓冲区有效数据长度 */
UINT8 ucBuf[CFG_DEFAULT_BUF_SIZE]; /* 缓冲区 */
} DEV_ARG_ST;
/*********************************************************************************************************
** 名称: getInfo
** 输入: cmd 命令参数
** opti 参数索引
** 输出: 错误码,NONE成功,ERROR错误
** 说明: 获取 info 选项
*********************************************************************************************************/
static int getInfo (command_st *cmd, int opti)
{
DEV_ARG_ST *pArg;
pArg = cmd->priv;
printf("echo = %d\n", pArg->uiEchoState);
printf("display = %d\n", pArg->uiDisplayState);
printf("iFd = %d\n", pArg->iFd);
printf("tidRecv = %08x\n", (UINT)pArg->tidRecv);
printf("tidEcho = %08x\n", (UINT)pArg->tidEcho);
printf("tidSend = %08x\n", (UINT)pArg->tidSend);
printf("file = %s\n", pArg->cFileName);
printf("uiBaud = %d\n", pArg->uiBaud);
printf("uiOption = %08x\n", pArg->uiOption);
printf("recvCount= %d\n", pArg->uiRecvCount);
printf("echoCount= %d\n", pArg->uiEchoCount);
printf("sendCount= %d\n", pArg->uiSendCount);
printf("interval = %d\n", pArg->uiSendInterval);
printf("number = %d\n", pArg->uiSendNumber);
printf("uiLength = %d\n", pArg->uiBufLength);
if (pArg->uiBufLength) {
PRINT_DATA_UINT8(pArg->ucBuf, pArg->uiBufLength, "send data :");
}
return (ERROR);
}
/*********************************************************************************************************
** 名称: getClose
** 输入: cmd 命令参数
** opti 参数索引
** 输出: 错误码,NONE成功,ERROR错误
** 说明: 获取 Option 选项
*********************************************************************************************************/
static int getOption (command_st *cmd, int opti)
{
option_st *opt;
DEV_ARG_ST *pArg;
char *str;
pArg = cmd->priv;
opt = &cmd->optv[opti];
if (opt->argc <= 0) {
return (NONE);
}
str = cmd->argv[opt->index + 1];
if (strcmp(str, "none") == 0) {
pArg->uiOption = CREAD | CS8;
} else if (strcmp(str, "odd") == 0) {
pArg->uiOption = CREAD | CS8 | PARENB | PARODD;
} else if (strcmp(str, "even") == 0) {
pArg->uiOption = CREAD | CS8 | PARENB;
} else {
pArg->uiOption = strtoul(str, NULL, 0);
}
return (NONE);
}
/*********************************************************************************************************
** 名称: devPthreadRecv
** 描述: 串口接收线程
** 输入: pvArg 参数结构
** 输出: ERROR_CODE
*********************************************************************************************************/
static void *devPthreadRecv (void * pvArg)
{
INT res;
UINT8 buf[CFG_DEFAULT_BUF_SIZE];
DEV_ARG_ST *pArg = (DEV_ARG_ST *)pvArg;
while(1) {
res = read(pArg->iFd, buf, sizeof(buf));
if (res > 0) { /* 接收数据 */
pArg->uiRecvCount += res;
if (pArg->uiEchoState) {
Lw_MsgQueue_SendEx2(pArg->hQueueSend,
(PVOID)buf,
res,
LW_OPTION_NOT_WAIT,
LW_OPTION_DEFAULT);
}
if (pArg->uiDisplayState) {
PRINT_DATA_UINT8(buf, res, "[uart %s] recv %d byte, data :", pArg->cFileName, res);
}
} else {
PRINT_ERROR("read err,res = %d \n", res);
break;
}
}
return (NULL);
}
/*********************************************************************************************************
** 名称: devPthreadEcho
** 描述: 串口反射线程
** 输入: pvArg 参数结构
** 输出: ERROR_CODE
*********************************************************************************************************/
static void *devPthreadEcho (void * pvArg)
{
INT res;
size_t txLen;
UINT8 buf[CFG_DEFAULT_BUF_SIZE];
DEV_ARG_ST *pArg = (DEV_ARG_ST *)pvArg;
while(1) {
res = Lw_MsgQueue_Receive(pArg->hQueueSend,
buf,
CFG_DEFAULT_BUF_SIZE,
&txLen,
LW_OPTION_WAIT_INFINITE);
if (res < 0) {
PRINT_ERROR("Lw_MsgQueue_Receive,res = %d", res);
break;
} else { /* 接收数据 */
if (pArg->uiDisplayState) {
pArg->uiEchoCount += txLen;
write(pArg->iFd, buf, txLen);
PRINT_DATA_UINT8(buf, txLen, "[uart %s] echo %d byte, data :", pArg->cFileName, txLen);
}
}
}
return (NULL);
}
/*********************************************************************************************************
** 名称: devPthreadSend
** 描述: 串口发送线程
** 输入: pvArg 参数结构
** 输出: ERROR_CODE
*********************************************************************************************************/
static void *devPthreadSend (void * pvArg)
{
DEV_ARG_ST *pArg = (DEV_ARG_ST *)pvArg;
while(pArg->uiBufLength && pArg->uiSendNumber--) {
if (pArg->uiDisplayState) {
PRINT_DATA_UINT8(pArg->ucBuf, pArg->uiBufLength,
"[uart %s] send %d byte, data :", pArg->cFileName, pArg->uiBufLength);
}
pArg->uiSendCount += pArg->uiBufLength;
write(pArg->iFd, pArg->ucBuf, pArg->uiBufLength);
usleep(pArg->uiSendInterval * 1000);
}
return (NULL);
}
/*********************************************************************************************************
** 名称: uartTool
** 输入: argc 参数个数
** argv 参数列表
** 输出: 错误码,NONE成功,ERROR错误
** 说明: 通用串口测试工具
*********************************************************************************************************/
static int uartTool (int argc, char *argv[])
{
DEV_ARG_ST devArg = {
.uiEchoState = 0,
.uiDisplayState = 1,
.iFd = -1,
.cFileName = "/dev/ttyS1",
.uiBaud = SIO_BAUD_115200,
.uiOption = CREAD | CS8,
.uiRecvCount = 0,
.uiEchoCount = 0,
.uiSendCount = 0,
.uiSendInterval = 1000,
.uiSendNumber = 0,
.uiBufLength = 0,
.ucBuf = { 0 },
};
option_st optv[] = {
{ATT_BUF_SIZE(sizeof(devArg.cFileName)) |
ATT_TYPE_SBUF, &devArg.cFileName, "f", "set uart device file name"},
{ATT_TYPE_BOOL, &devArg.uiDisplayState, "d", "set dispaly state"},
{ATT_TYPE_BOOL, &devArg.uiEchoState, "e", "set echo state"},
{ATT_TYPE_U32, &devArg.uiBaud, "b", "set baudrate"},
{ATT_FUNC, getOption, "o", "set option, none/odd/even/0x0e"},
{ATT_TYPE_U32, &devArg.uiSendInterval, "i", "set send interval, ms"},
{ATT_TYPE_U32, &devArg.uiSendNumber, "n", "set send number"},
{ATT_ARRAY(GET_ARRAY_COUNT(devArg.ucBuf)) |
ATT_TYPE_U8, &devArg.ucBuf, "w", "set send data", &devArg.uiBufLength},
{ATT_FUNC, getInfo, "info", "print cmd info"},
};
command_st cmd;
DEV_ARG_ST *pArg = &devArg;
int err;
LW_CLASS_THREADATTR hThreadAttr;
cmd.argc = argc;
cmd.argv = argv;
cmd.optc = GET_OPTC(optv);
cmd.optv = optv;
cmd.priv = pArg;
err = cmdParse(&cmd);
if (err != NONE) {
return (err);
}
pArg->hQueueSend = Lw_MsgQueue_Create(
"uartQueue",
CFG_DEFAULT_BUF_COUNT,
CFG_DEFAULT_BUF_SIZE,
LW_OPTION_WAIT_FIFO | LW_OPTION_OBJECT_LOCAL,
NULL);
pArg->iFd = open(pArg->cFileName, O_RDWR, 0666);
if (pArg->iFd < 0) {
PRINT_ERROR("open device [%s] faild\n", pArg->cFileName);
return (PX_ERROR);
}
ioctl(pArg->iFd, FIOFLUSH, NULL); /* 清空设备收发缓冲区 */
if (ioctl(pArg->iFd, SIO_BAUD_SET, pArg->uiBaud) != ERROR_NONE) {
PRINT_ERROR("set baudrate faild\n");
close(pArg->iFd);
return (PX_ERROR);
}
if (ioctl(pArg->iFd, SIO_HW_OPTS_SET, pArg->uiOption) != ERROR_NONE) {
PRINT_ERROR("set opt faild\n");
close(pArg->iFd);
return (PX_ERROR);
}
Lw_ThreadAttr_Build(&hThreadAttr, 4 * LW_CFG_KB_SIZE, LW_PRIO_NORMAL + 0, 0, (void *)pArg);
Lw_Thread_Create("t_uartRecv", devPthreadRecv, &hThreadAttr, &pArg->tidRecv);
Lw_ThreadAttr_Build(&hThreadAttr, 4 * LW_CFG_KB_SIZE, LW_PRIO_NORMAL + 1, 0, (void *)pArg);
Lw_Thread_Create("t_uartEcho", devPthreadEcho, &hThreadAttr, &pArg->tidEcho);
Lw_ThreadAttr_Build(&hThreadAttr, 4 * LW_CFG_KB_SIZE, LW_PRIO_NORMAL + 2, 0, (void *)pArg);
Lw_Thread_Create("t_uartSend", devPthreadSend, &hThreadAttr, &pArg->tidSend);
Lw_Thread_Join(pArg->tidRecv, NULL);
Lw_Thread_Join(pArg->tidEcho, NULL);
return (ERROR_NONE);
}
/*********************************************************************************************************
** 名称: main
** 描述: 程序入口
** 输入: argc 参数个数
** argv 参数列表
** 输出: ERROR_CODE
*********************************************************************************************************/
int main (int argc, char **argv)
{
uartTool(argc, argv);
return (0);
}
/*********************************************************************************************************
END
*********************************************************************************************************/