本文中,我们讨论如何在Matlab中进行参数解析。
参数解析对于软件开发和程序设计至关重要。在Matlab中,函数参数传递一般采用直接传值方式,最复杂的情况下也就是使用varargin变长数组。那么如何对varargin的数据进行参数解析呢。我总结了以下3种形式:
1、键值对形式的参数
在Matlab中,最常见的参数形式是形如”PropertyName”, “PropertyValue”的键值对参数。例如:
h = figure('Name', 'Performance', 'NumberTitle', 'off', 'Menubar', 'none', 'Toolbar', 'none');
这类形式的参数由于结构固定,所以解析起来也比较容易,可以直接利用Matlab代码实现。
function [opts, args] = argparse(varargin)
% [opts, args] = argparse(opts, 'PropertyName', 'PropertyValue', ...)
% [opts, args] = argparse('PropertyName', 'PropertyValue', ...)
% bigben@seu.edu.cn
if isempty(varargin); return; end;
if isstruct(varargin{1})
opts = varargin(1);
args = varargin(2:end);
else
opts =struct;
args = varargin(:);
end
n = numel(varargin);
assert(rem(n, 2) == 0)
args = varargin(:);
for i = 1:2:n
assert(ischar(args{i}), 'Property name should be a string');
opts.(args{i}) = args{i+1};
end
args = args(i+2:end);
end
然而,如果我们严格限定我们在程序设计过程中只使用这种键值对形式的参数,那么本文的讨论到此即可以结束了。但是,面对现实生活中复杂的应用场景,这种处理方式也有一些弊端。
在其他语言程序开发中常见的一种命令行参数传递方式类似下面这样:
gcc --help
或者
gcc -v -g -I/usr/include -L/usr/lib -lm -o main.exe main.c
这种使用方式带来了更大的灵活性。例如,gcc --help
中的--help
选项指定程序打印帮助信息。如果利用Matlab默认的键值对方式实现,可能要写成下面这样:
gcc('help', 'on')
这看起来会觉得很别扭。因为这里--help
只是一个开关,在Matlab中却要生硬的给他一个值on
或者off
。相比之下,C语言风格的命令行参数显得更加灵活和优雅。
可能有人会说,那种风格的参数是面向应用程序的,Matlab中没有这种需求。所以用不着这种风格的参数,可是事实果真如此吗。加入你用Matlab代码编写了一个小型应用软件,你希望将其build为一个独立的可执行文件。利用Matlab的mcc,将Matlab代码build为一个standalone application并不是难事。但是,作为一个应用程序,指望他不接受任何参数似乎并不现实。而对于命令行程序,用户熟悉的是C风格的命令行参数,而不是Matlab风格的参数。想象一下,当你把你的app.m编译为一个exe文件,用户需要打印帮助信息时,却被提示需要输入app help on
这样的命令时他是多么诧异。
所以,下面我们来讨论一下在Matlab中如何解析C风格的命令行参数。
2、利用Java中的第三方参数解析库
由于Matlab提供对java的支持,而java有很多简单易用而又功能强大的命令行参数解析工具包,例如CmdOption,args4j等等。因而,Matlab中可以调用这些java工具包完成命令行参数解析,可以参考我的另一篇博客Matlab/Octave中使用Java。
3、利用c语言getopt库完成参数解析
C语言风格的参数解析比较经典的库是getopt,这个已经成为mingw的一部分而随软件自带了,只需在用户源码中#include <getopt.h>
即可使用。为了在Matlab中也能调用该库,可以使用mex编程方式来实现。
mexgetopt的接口设计参考了python中的getopt模块。
python中的getopt常用方法如下所示:
import getopt, sys
try:
[opts, args] = getopt.getopt(sys.argv[1:], "hvc:f:", \
["help", "version", "config=", "compiler="])
except getopt.GetoptError as e:
print e
sys.exit(-1)
for opt, val in opts:
if opt in ["-h", "--help"]:
pass
elif opt in ["-v", "--version"]:
pass
elif opt in ["-f", "--config"]:
config = val
else:
print("Invalid option: %s" %(opt))
getopt接收3个输入参数,第一个参数为输入的命令行参数的组成的字符串列表。第二个参数为一个字符串,用于描述短选项,即以’-‘开头的选项,例如-v
、-h
等等,描述字符串中每个字符代表一个选项,字符后面如果由一个冒号则表明该选项有参数。第3个参数用于描述长选项,即以’–’开头的选项,例如--version
、--help
等等。这是一个字符串列表,每个字符串是一个长选项,若字符串以等号结尾,则表明该选项有参数。
getopt的输出参数有两个。第一个参数是一个字典,每一项是一个元组,第一项为接收到的选项,第二项为选项对应的参数。若没有参数则为空。第二个参数参数之后的部分。
一般一个应用程序的参数都是这么布局的:
program [options] [args]
mexgetopt的接口设计也采用这种模式,输入分为两个部分,一个部分是输入参数组成的列表,另一个适用于描述短选项的字符串。
输入参数有两种方式,一种是一个cell形式的字符串数组,另一种则是以变长参数的形式。如,以下两种调用格式都是合法的。
mexgetopt({'-h', '-v', '-v', 'abc.txt'}, 'hvf:')
mexgetopt('-h', '-v', '-v', 'abc.txt', 'hvf:')
输出参数与python的getopt模块相同。
目前mexgetopt只支持短选项解析,暂不支持长选项解析。代码如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <getopt.h>
#include "mex.h"
// valid short options spec string is composed of alphabet, numbers and ':'.
int check_short_options(const char* short_options) {
const char* p = short_options;
int flag = 1;
while (*p) {
if (!((isalnum(*p)) || (*p == ':'))) {
flag = 0;
break;
}
p++;
}
return flag;
}
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
if (nrhs < 1 || nlhs > 2) {
mexErrMsgTxt("mexgetopt:SyntaxError: [opts, args] = mexgetopt(arg_string, option_specs)\n");
}
opterr = 1;
optind = 0;
int argc = 0;
char** argv = NULL;
char* short_options = NULL;
if (mxIsCell(prhs[0])) {
const mxArray* arg_string = prhs[0];
argc = mxGetNumberOfElements(arg_string) + 1;
argv = (char **) malloc(argc * sizeof(char *));
argv[0] = NULL;
for (size_t i = 1; i < argc; ++i) {
argv[i] = mxArrayToString(mxGetCell(arg_string, i - 1));
}
if (nrhs > 1) {
short_options = mxArrayToString(prhs[1]);
if (0 == check_short_options(short_options)) {
free(argv);
mexErrMsgTxt("mexgetopt:ValueError: invalid short options specs\n");
}
} else {
free(argv);
mexErrMsgTxt("mexgetopt:SyntaxError: mexgetopt(arg_string, option_specs)\n");
}
} else {
if (mxIsChar(prhs[nrhs-1])) {
short_options = mxArrayToString(prhs[nrhs-1]);
}
if (0 == check_short_options(short_options)) {
mexErrMsgTxt("mexgetopt:ValueError: invalid short options specs\n");
}
argc = nrhs - 1 + 1;
argv = NULL;
if (argc > 0) {
argv = (char **) malloc(argc * sizeof(char *));
}
argv[0] = NULL;
for (size_t i = 1; i < argc; ++i) {
argv[i] = mxArrayToString(prhs[i-1]);
}
}
int count = 0;
char ch;
while ((ch = getopt(argc, argv, short_options)) != EOF) {
if (ch == '?') {
mexErrMsgTxt("mexgetopt:ValueError: undefined option\n");
}
count++;
}
optind = 0;
if (nlhs > 0) {
char* field_names[] = {"opt", "arg"};
plhs[0] = mxCreateStructMatrix(1, count, 2, (const char**) field_names);
int i = 0;
char buf[10];
while ((ch = getopt(argc, argv, short_options)) != EOF) {
buf[0] = '-';
buf[1] = optopt;
buf[2] = 0;
mxSetField(plhs[0], i, "opt", mxCreateString(buf));
mxSetField(plhs[0], i, "arg", mxCreateString(optarg));
i++;
}
assert(i == count);
}
if (nlhs > 1) {
plhs[1] = mxCreateCellMatrix(1, argc - optind);
for (size_t i = optind; i < argc; ++i) {
mxSetCell(plhs[1], i - optind, mxCreateString(argv[i]));
}
}
free(argv);
}
Makefile如下
MATLABROOT ?= D:\Program Files\MATLAB\R2010a
MEXLIBS ?= -llibmx -llibmex -llibut
MEXEXT ?= $(shell mexext)
CC = g++
CCFLAGS =
CCFLAGS += -I"${MATLABROOT}/extern/include" -L"${MATLABROOT}/extern/lib/win32/microsoft"
target = mexgetopt.$(MEXEXT)
all: target
target: mexgetopt.c
@$(CC) $(CCFLAGS) -shared -o $@ $(CCFLAGS) $^ ${MEXLIBS}
clean:
@rm *.$(MEXEXT) *.o
以上的代码封装了对getopt的调用,今后还会继续实现对getopt_long以及getopt_long_only的封装。项目主页在mexgetopt,欢迎志同道合的朋友们贡献源码。