在Linux中,用命令行执行可执行文件时,常常涉及到:大量、不同类型、不同形式的 输入参数问题。从简单的说起,现在假设有我们一个用户定义的可执行程序,名为test.sh,它需要3个输入参数,于是我们通过命令行去执行它的时候,往往通过如下的做法:

1
./test.sh 1 2 3

一. main函数

上面的./test.sh是执行程序,1、2、3test.sh的输入参数,这些命令项通过传递给程序的main函数进行处理,main函数的一般形式如下:

1
int main(int argc, char *argv[]);

argc是一个整型,argv是一个指针数组,argc记录argv的大小,例如./test.sh 1 2 3将被以如下的方式传递:

1
2
3
4
5
argc=4
argv[0]=./test.sh;
argv[1]=1;
argv[2]=2;
argv[3]=3;

二. getopt函数

现在我们考虑更复杂一些的输入要求,还是以test.sh为例,不过这时它的输入参数要更多一些了

1
./test.sh -1 -2 -3 -a 4 -b 5 -c 6 -Q -S -T  // 此处仅作为示例,还可以有更多更复杂参数,见后续实例

先说说这一行参数表示什么意思,这里的破折号-表示这是一个控制选项,例如-1,在此处1是单字符选项,而-a 4表示带参数的选项,a是该选项的标识符,4是随同该选项一同传入的参数,-Q,与-1一样,Q也是单字符选项,没有随同的输入参数。通过定义不同的选项,我们可以在test.sh中定义丰富的操作完成各种各样的计算任务,但是这个时候main函数可没有办法给你完成上面的解析工作,main函数只是将./test.sh -1 -2 -3 -a 4 -b 5 -c 6 -Q -S -T传递进来,保存在argc和argv里,至于这些参数如何分配并对应什么操作则是需要用户自行定义了,当然,自己写解析函数是可行的,但是有更好的选择。

在C语言中,unistd.h提供的getopt()这个函数,结合switch语句,可以帮助我们方便实现参数解析。

先看例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
……
int opt;
while ( (opt=getopt(argc, argv, "123a:b:c:QST")) != -1 )
{
switch (opt)
{
case '1':
para1 = 1;
break;
case '2':
para2 = 2;
break;
case '3':
para3 = 3;
break;
case 'a':
para4 = atof(optarg);
break;
case 'b':
para4 = atof(optarg);
break;
case 'c':
para5 = atof(optarg);
break;
case 'Q':
definedOption1 = true;
break;
case 'S':
definedOption2 = true;
break;
case 'T':
definedOption3 = true;
break;
……
}
……
}
……

getopt()函数原型为:

1
int getopt(int argc, char * const argv[], const char *optstring);

使用getopt函数需要包含以下头文件:

1
2
#include <unistd.h>
#include <getopt.h>

有几个全局变量与getopt函数解析参数有关:
optind:int型, 指示下一个要解析的参数位置,初始时为1。
optarg:char *, 必须接参数的选项元素的参数, optarg 就指向参数字符串。
opterr: int 型, 设为0将不打印错误信。

int argc, char * const argv[]一般是直接通过读取main函数的argc和argv,而optstring则是用户定义的选项字符,例如在上面的例子中,optstring是 123a:b:c:QST ,它用来解析输入参数./test.sh -1 -2 -3 -a 4 -b 5 -c 6 -Q -S -T,并且是由用户定义。

字符串optstring的元素一般可分为下面几种:

  • 单个字符,表示选项,123a:b:c:QST中的1、2、3、Q、S、T都是单字符选项
  • 单个字符后接一个冒号:表示该选项后必须跟一个参数。参数紧跟在选项后以空格隔开。该参数的指针赋给optarg。123a:b:c:QST中的a:, b:, c: 都表示它们需要附加指定输入参数,这就是为什么在输入参数时是-a 4 -b 5 -c 6的缘故
  • 单个字符后跟两个冒号::表示该选项后可选地跟一个参数。参数必须紧跟在选项后不能以空格隔开。该参数的指针赋给optarg。(这个特性是GNU的扩张,本例中不作介绍)。

update@2016年5月17日17:11:51
一个选项的识别符号只能是单个字符(或加上与一个冒号:或两个冒号::的组合),不能用多个字符来表示一个选项。
例如这样做就会识别出错:
用户输入的参数./test.sh -1 -2 -3 -a1 4 -b 5 -c 6 -Q -S -T,那么它对应的optstring则是 123a1:b:c:QST,显然其中a1为两个字符,想让它表示一个选项,是会出现问题的。

同时,getopt()在unistd.h中的相关定义:

1
2
3
4
5
* extern char *optarg;  //选项的参数指针
* extern int optind, //下一次调用getopt的时,从optind存储的位置处重新开始检查选项。
* extern int opterr, //当opterr=0时,getopt不向stderr输出错误信息。
* extern int optopt; //当命令行选项字符不包括在optstring中或者选项缺少必要的参数时,
//该选项存储在optopt中,getopt返回'?'

那么现在就清楚用户输入的参数./test.sh -1 -2 -3 -a 4 -b 5 -c 6 -Q -S -T是怎么进行传递和解析的了:

  • 通过main函数将输入参数./test.sh -1 -2 -3 -a 4 -b 5 -c 6 -Q -S -T保存在argc和argv
  • getopt()按照123a:b:c:QST这个规则去解析argc和argv中保存的数据
  • 例如,首先去./test.sh -1 -2 -3 -a 4 -b 5 -c 6 -Q -S -T读取到的选项是-1(第一个输入参数./test.sh是执行程序是名称不予考虑),于是就去123a:b:c:QST中检查是否有1这个选项,有的话就返回该选项(这个时候就会转入相应的case执行对应的操作),同时将选项索引optind更新为输入参数的下一个位置(此处为-2的位置)作为下次搜索的开始位置,如果在optstring里没有找到1,例如我们的optstring是23a:b:c:QST,即当命令行选项字符不包括在optstring中或者选项缺少必要的参数时,该选项存储在optopt中,getopt返回'?',并从optind开始进行下一个输入参数的解析
  • 继续解析,当解析到-a时,这个时候getopt()发现optstring里的a后面跟着:于是它知道a是还需要传递进来一个指定的参数,于是就将指针*optarg指向-a后面的一个参数即是4,这样返回选项a的时候,a所对应的参数值此时由optarg指向,这样转入case 'a'的时候就可以对该参数进行相应的操作了

    1
    2
    3
    case 'a':
    para4 = atof(optarg);
    break;
  • 接着往下走,-Q -S -T与前面的-1是一样的,都是不带参数的单字符选择,当检查完-T后,返回-1,表示检查完毕,这个时候就完成了对./test.sh -1 -2 -3 -a 4 -b 5 -c 6 -Q -S -T中所有参数的分配工作。

上述简单介绍了命令行多参数解析时getopt()函数的用法,若有错误,欢迎斧正与探讨;若干概念引自文献[1],若有需要相关概念更详细的解释,可前往阅读。

三. 实例

在Eureka中进行的FSI数值算例中用到如下控制选项,整理出来作为备忘。

1
2
3
4
5
6
7
输入
./$1 -2 -L 290 -H 120 -J 0.1 -h 8.0 -D 500 -r 1.5 -c 1.0e-5 -x 3.1 -b 1.6 -q 1 -R 1 -M 0.01
-U -Q -t 0.1 -T 0.1 -d 1000 -v 513 -f 1 -G 1.0e7 -k 1.42e5 -p 1.18e-6 -g 1.4 -m 0 -s 1.82e-5
-i 0.1 -l 10.0 -I 0.0 -A 0.0 -E 2.5e6 -P 1e-4 -N 0.35 -n 8 -K 10.0

解析
getopt(argc, argv, "23L:H:W:J:h:D:r:c:x:b:q:R:M:SQUt:T:d:v:f:G:k:p:g:m:s:i:l:I:A:E:P:N:n:K:");
控制符 含义
./$1 执行程序名称,e.g. verification_oscillation_structure.gcc_v714M.st
-2 2 二维
-3 3 三维
-L 290 Length
-H 120 Height
-W 0 Width
-J 0.1 critical_shear/maximum shear strain
-h 8 element size that determines the patch thickness 入口单元尺寸
-D 500 Domain_End 计算域终点,计算域起点为0.0
-r 1.5 searchRange, parameters for the shape function
-c 1.0e-5 cutoff, parameters for the shape function
-x 3.1 extension, parameters for the shape function
-b 1.6 beta, parameters for the shape function
-q 1 integration_order q=1表示单元的中心插入1个质量点
-R 1 nRing 邻域控制,1表示从单元本身节点开始,如果变形大,可以设置为2
-M 0.01 mass_factor
-S true adaptive_search
-Q true adaptive_beta
-U true updateNeighbor
-t 0.1 time step ratio 0.1表示10%
-T 0.1 total_time simulation time
-d 1000 dump,number of time steps to visualize the results
-v 513 max velocity of inflow,speed
-f 1.0 parameters for the rupture of solids,epsilon_h=1 包含裂纹扩展
-G 1.0e7 parameters for the rupture of solids
-k 1.42e5 the bulk modulus of fluid (2.1e9 Pa for water) 体积模量
-p 1.18e-6 the density of fluid (1.0e3 kg/m^3 for water)
-g 1.4 Gruneisen parameter in the equation of state of the fluid
-m 0 nuf,poisson’s ratio of the fluid
-s 1.82e-5 the shear viscosity coefficient of fluid (1.82e-5kg/(m.s))动力粘度
-i 0.1 artificial viscosity coefficient to stablize the simulation 经验系数
-l 10 artificial viscosity coefficient to stablize the simulation 经验系数
-I 0.0 artificial viscosity coefficient to stablize the simulation 经验系数
-A 0.0 artificial viscosity coefficient to stablize the simulation 经验系数
-E 2.5e6 E_Solid,Young’s modulus of solid 固体材料弹性模量
-P 1.0e-4 rho_Solid,density of solid 固体材料密度
-N 0.35 nu_Solid,Poisson’s ratio of solid 固体材料泊松比
-n 8 number of threads 线程数
-K 10

四. 参考文献

[1] http://www.gnu.org/software/libc/manual/html_node/Using-Getopt.html#Using-Getopt

2016年2月1日16:18:12
于克利夫兰