Linux 程序设计学习笔记-命令行参数处理

Linux 程序设计学习笔记----命令行参数处理

转载请注明出处.http://blog.csdn.net/suool/article/details/38089001

问题引入----命令行参数及解析

在使用linux时,与windows最大的不同应该就是经常使用命令行来解决大多数问题.比如下面这样的:

Linux 程序设计学习笔记-命令行参数处理

而显然我们知道C语言程序的入口是mian函数,即是从main函数开始执行,而main函数的原型是:

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

程序的 main 函数可以通过参数 argc 和 argv 来访问程序的参数列表(如果你不需要访问参数列表,你可以直接忽略它们)。第一个参数 argc 指示了命令行中参数的数量(这个值包含命令本身,如果后面没有参数,则值为1)。

第二个参数 argv 是一个字符串数组。其各个成员分别指向各个参数,即argv[0]指向命令本身,具体见下图.数组的大小由 argc 指定,而数组的元素则为各个命令行参数
的元素,表示以 NULL 结束的字符串形式。
使用命令行参数的过程因此被简化为检查 argc 和 argv 的内容。如果你对程序自己的名
字没有兴趣,记得跳过第一个参数.

Linux 程序设计学习笔记-命令行参数处理

下面的程序演示使用 argc 和 argv 的方法:

/*************************************************************************
	> File Name: arglist.c
	> Author: suool
	> Mail: 1020935219@qq.com 
	> Created Time: 2014年07月24日 星期四 16时35分40秒
 ************************************************************************/   
#include <stdio.h>
int main (int argc, char* argv[])
{
    printf ("The name of this program is '%s'.\n", argv[0]);
    printf ("This program was invoked with %d arguments.\n", argc - 1);
    /* 指定了命令行参数么?*/
    if (argc > 1) 
    {
        /* 有,那么输出这些参数。*/
        int i;
        printf ("The arguments are:\n");
        for (i = 1; i < argc; ++i)
        printf (" %s\n", argv[i]);
    }
    return 0;
}
结果如下:

Linux 程序设计学习笔记-命令行参数处理
Linux 程序设计学习笔记-命令行参数处理

几乎所有 GNU/Linux 程序都遵循一些处理命令行参数的习惯。程序期望得到的参数可以被分为两种: 选项( options ,又作 flags ) 和其它(非选项)参数。选项用于调整程序的
行为方式,而其它参数提供了程序的输入(例如,输入文件的名字)。

选项通常有两种格式:
· 短选项( short options ) 由一个短杠(hyphen)和一个字符(通常为一个大写或小写字母)组成。短选项可以方便用户的输入。
· 长选项( long options ) 由两个短杠开始,之后跟随一个由大小写字母和短杠组成的名字。长选项方便记忆和阅读(尤其在脚本中使用的时候)
通常程序会为它支持的选项提供长短两种形式,前者为便于用户理解,而后者为简化输入。例如,多数程序都能理解 –h 和 –help 这两个参数,并以相同方法进行处理。一般而
言,当从 shell 中调用一个程序的时候,程序名后紧跟的就是选项参数。如果一些选项需要一个参数,则参数紧跟在选项之后。例如,许多程序将选项 –output foo 解释为“将输出文
件设置为 foo”。

在选项之后可能出现其它命令行参数,通常用于指明输入文件或输入数据。例如,命令行 ls –s / 列举根目录的内容。选项 –s 改变了 ls 的默认行为方式,通知它为每个条目显示文件大小(以 KB 为单位)。参数 / 向 ls 指明了被列举的目录。选项 –size 与–s 选项具有相同的含义,因此调用 ls –size / 会得到完全相同的结果。

这时候我们就会发现一个问题,那就是如果参数比较多,而且要有一定的顺序怎么办?如何解析命令行的参数,判断参数的位置和正确与否呢?

这就是这次要讲的东西.

命令行参数识别

简单命令行处理getopt获取并解析命令行参数

getopt() 函数位于 unistd.h 系统头文件中,其原型如示:

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

给定了命令参数的数量 (argc)、指向这些参数的数组 (argv) 和选项字符串 (optstring) 后,getopt() 将返回第一个选项,并设置一些全局变量。使用相同的参数再次调用该函数时,它将返回下一个选项,并设置相应的全局变量。如果不再有识别到的选项,将返回 -1,此任务就完成了。

getopt() 所设置的全局变量包括:

  • optarg——指向当前选项参数(如果有)的指针。
  • optind——再次调用 getopt() 时的下一个 argv 指针的索引。
  • optopt——最后一个已知选项。

对于每个选项,选项字符串 (optstring) 中都包含一个对应的字符。具有参数的选项后面跟有一个 : 字符。

其中optstrings 可以使下面的元素:

  • 1.单个字符,表示选项
  • 2.单个字符后面一个冒号:表示该选项后面必须跟一个参数.参数紧跟在选项后面或者以空格隔开,该参数的指针赋给optarg.
  • 3.单个字符后面跟两个冒号:: ,表示该选项后面可以跟一个参数.参数必须紧跟在选项后面不能以空格隔开.该参数一样赋给optarg.

如,如果opstrings = "ab:c::d::",命令行参数如下:

./getopt -a -b host -chello -d world.
这个命令行参数中,去掉短参数的-,其中a,b,c就是选项,host是b的参数,hello是c的参数,但是world不是d的参数,因为有空格隔开.

可以重复调用 getopt(),直到其返回 -1 为止;任何剩下的命令行参数通常视为文件名或程序相应的其他内容。

每执行一次,getopt函数将返回查找到的命令行输入的参数字符,并更新系统的全局变量,默认情况下,getopt函数会重新排列命令行参数的顺序,所有不可知的或者错误的命令行参数都排列到最后,getopt将返回-1,同时optind存储第一个未知的或者出错的选项的下标.

下面我们看两个示例程序.

1.有一个程序有三个选项a,b,c,其中b必须带参数,而c可以带可以不带.请写一个程序使用getopt来解析这个程序的命令行参数.

下面是代码示例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int result;
    opterr = 0;             // 不输出错误信息
    while( (result = getopt(argc, argv, "ab:c::")) != -1 )
    {                         // 一直解析
           switch(result)
          {
               case 'a':
                   printf("option=a, optopt=%c, optarg=%s\n", optopt, optarg);
                   break;
              case 'b':
                   printf("option=b, optopt=%c, optarg=%s\n", optopt, optarg);
                   break;
              case 'c':
                   printf("option=c, optopt=%c, optarg=%s\n", optopt, optarg);
                   break;
              case '?':
                    printf("result=?, optopt=%c, optarg=%s\n", optopt, optarg);
                    break;
              default:
                   printf("default, result=%c\n",result);
                   break;
           }
        printf("argv[%d]=%s\n", optind, argv[optind]);
    }
    printf("result=-1, optind=%d\n", optind);    // 打印最后有可能出错的位置

    for(result = optind; result < argc; result++)   // 打印余下的错误选项
         printf("-----argv[%d]=%s\n", result, argv[result]);
    for(result = 1; result < argc; result++)        // 打印重新排列的选项列表
          printf("\nat the end-----argv[%d]=%s\n", result, argv[result]);
    return 0;
}

运行结果如下:

第一次命令行参数为:

./getopt_exp -a host -b hello -cworld -d
解析的结果为:

Linux 程序设计学习笔记-命令行参数处理

其他的请自己尝试吧,可以尝试一下没有错误的,或者更加错误的,

2.一个假想的 doc2html 程序的命令行处理。该 doc2html 程序将某种类型的文档转换为 HTML,具体由用户指定的命令行选项控制。它支持以下选项:

  • -I——不创建关键字索引。
  • -l lang——转换为使用语言代码 lang 指定的语言。
  • -o outfile.html——将经过转换的文档写入到 outfile.html,而不是打印到标准输出。
  • -v——进行转换时提供详细信息;可以多次指定,以提高诊断级别。
  • 将使用其他文件名称来作为输入文档。

还将支持 -h 和 -?,以打印帮助消息来提示各个选项的用途。

先将代码贴如下:

/* getopt_demo - demonstrate getopt() usage
 *
 * This application shows you one way of using getopt() to
 * process your command-line options and store them in a
 * global structure for easy access.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

/* doc2html supports the following command-line arguments:
 * 
 * -I - don't produce a keyword index
 * -l lang - produce output in the specified language, lang
 * -o outfile - write output to outfile instead of stdout
 * -v - be verbose; more -v means more diagnostics
 * additional file names are used as input files
 * 
 * The optString global tells getopt() which options we
 * support, and which options have arguments.
 */
struct globalArgs_t {
	int noIndex;				/* -I option */
	char *langCode;				/* -l option */
	const char *outFileName;	/* -o option */
	FILE *outFile;
	int verbosity;				/* -v option */
	char **inputFiles;			/* input files */
	int numInputFiles;			/* # of input files */
} globalArgs;

static const char *optString = "Il:o:vh?";

/* Display program usage, and exit.
 */
void display_usage( void )
{
	puts( "doc2html - convert documents to HTML" );
	/* ... */
	exit( EXIT_FAILURE );
}

/* Convert the input files to HTML, governed by globalArgs.
 */
void convert_document( void )
{
	/* ... */
}

int main( int argc, char *argv[] )
{
	int opt = 0;
	
	/* Initialize globalArgs before we get to work. */
	globalArgs.noIndex = 0;		/* false */
	globalArgs.langCode = NULL;
	globalArgs.outFileName = NULL;
	globalArgs.outFile = NULL;
	globalArgs.verbosity = 0;
	globalArgs.inputFiles = NULL;
	globalArgs.numInputFiles = 0;
	
	/* Process the arguments with getopt(), then 
	 * populate globalArgs. 
	 */
	opt = getopt( argc, argv, optString );
	while( opt != -1 ) {
		switch( opt ) {
			case 'I':
				globalArgs.noIndex = 1;	/* true */
				break;
				
			case 'l':
				globalArgs.langCode = optarg;
				break;
				
			case 'o':
				/* This generates an "assignment from
				 * incompatible pointer type" warning that
				 * you can safely ignore.
				 */
				globalArgs.outFileName = optarg;
				break;
				
			case 'v':
				globalArgs.verbosity++;
				break;
				
			case 'h':	/* fall-through is intentional */
			case '?':
				display_usage();
				break;
				
			default:
				/* You won't actually get here. */
				break;
		}
		
		opt = getopt( argc, argv, optString );
	}
	
	globalArgs.inputFiles = argv + optind;
	globalArgs.numInputFiles = argc - optind;

	convert_document();
	
	return EXIT_SUCCESS;
}
下面分解上面的代码:

头文件:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

创建的 globalArgs 结构,用于以合理的方式存储命令行选项。由于这是个全局变量,程序中任何位置的代码都可以访问这些变量,以确定是否创建关键字索引、生成何种语言等等事项。最好让 main() 函数外的代码将此结构视为一个常量、只读存储区,因为程序的任何部分都可以依赖于其内容。每个命令行选择都有一个对应的选项,而其他变量用于存储输出文件名、指向输入文件列表的指针和输入文件数量。

struct globalArgs_t 
{
    int noIndex;                /* -I option */
    char *langCode;             /* -l option */
    const char *outFileName;    /* -o option */
    FILE *outFile;
    int verbosity;              /* -v option */
    char **inputFiles;          /* input files */
    int numInputFiles;          /* # of input files */
} globalArgs;

static const char *optString = "Il:o:vh?";

选项字符串 optString 告知 getopt() 可以处理哪个选项以及哪个选项需要参数。如果在处期间遇到了其他选项,getopt() 将显示一个错误消息,程序将在显示了使用方法消息后退出。

下面的代码段包含一些从 main() 引用的用法消息函数和文档转换函数的小存根。可以对这些存根进行*更改,以用于更为有用的目的。

void display_usage( void )
{
    puts( "doc2html - convert documents to HTML" );
    /* ... */
    exit( EXIT_FAILURE );
}

void convert_document( void )
{
    /* ... */
}

最后,下面的代码段中所示,在 main() 函数中使用此结构。和优秀的开发人员一样,需要首先初始化 globalArgs 结构,然后才开始处理命令行参数。在程序中,可以借此设置在一定情况下合理的缺省值,以便在以后有更合适的缺省值时更方便地对其进行调整

int main( int argc, char *argv[] )
{
    int opt = 0;
    
    /* Initialize globalArgs before we get to work. */
    globalArgs.noIndex = 0;     /* false */
    globalArgs.langCode = NULL;
    globalArgs.outFileName = NULL;
    globalArgs.outFile = NULL;
    globalArgs.verbosity = 0;
    globalArgs.inputFiles = NULL;
    globalArgs.numInputFiles = 0;
下面的代码段中的 while 循环和 switch 语句是用于本程序的命令行处理的代码部分。只要 getopt() 发现选项,switch 语句将确定找到的是哪个选项,将能在 globalArgs 结构中看到具体情况。当 getopt() 最终返回 -1 时,就完成了选项处理过程,剩下的都是输入文件了。
opt = getopt( argc, argv, optString );
    while( opt != -1 ) {
        switch( opt ) {
            case 'I':
                globalArgs.noIndex = 1; /* true */
                break;
                
            case 'l':
                globalArgs.langCode = optarg;
                break;
                
            case 'o':
                globalArgs.outFileName = optarg;
                break;
                
            case 'v':
                globalArgs.verbosity++;
                break;
                
            case 'h':   /* fall-through is intentional */
            case '?':
                display_usage();
                break;
                
            default:
                /* You won't actually get here. */
                break;
        }
        
        opt = getopt( argc, argv, optString );
    }
    
    globalArgs.inputFiles = argv + optind;
    globalArgs.numInputFiles = argc - optind;

既然已经完成了参数和选项的收集工作,接下来就可以执行程序所设计的任何功能(在本例中是进行文档转换),然后退出

convert_document();
    return EXIT_SUCCESS;
}
当然上面的使用optget是简单的命令行处理,如果你要进行长参数的或者稍微复杂的处理,那就继续往下看吧.getopt_long() 是同时支持长选项和短选项的getopt() 版本。

复杂的命令行处理getopt获取并解析命令行参数

getlongopt的原型是:

int getopt_long (int argc, char *const *argv, const char *shortopts, const struct option *longopts, int *indexptr)
第一个参数是当前传递进来的参数个数,第二个参数是当前传递进来的参数列表,第三个参数是当前进程所有可支持的短参数的字符串,第四个参数是struct option,表示所有的长参数的对应关系.

结构体声明如下:

struct option 
{
   # if (defined __STDC__ && __STDC__) || defined __cplusplus
    const char *name;
   # else
    char * name;
   # endif
    int has_arg;   // 该参数是否需要带参数
    int *flag;     // 标志
    int val;       // 返回参数值
};

name 成员是指向长选项名称(带两个短横线)的指针。has_arg 成员设置为 no_argument、optional_argument, 或 required_argument(均在 getopt.h 中定义)之一,以指示选项是否具有参数。如果 flag 成员未设置为 NULL,在处理期间遇到此选项时,会使用 val 成员的值填充它所指向的 int 值。如果 flag 成员为 NULL,在 getopt_long() 遇到此选项时,将返回 val 中的值;通过将 val 设置为选项的 short 参数,可以在不添加任何其他代码的情况下使用 getopt_long()——处理 while loop 和 switch 的现有 getopt() 将自动处理此选项。
这已经变得更为灵活了,因为各个选项现在可以具有可选参数了。更重要的是,仅需要进行很少的工作,就可以方便地放入现有代码中。

此函数的返回情况如下:

1.在使用此函数处理一个参数时,全局变量optarg指向下一个要处理的变量,并返回struct option的第四个成员.一般情况下,如果struct option的第三个参数位置设置null,第四个参数一般设置为该长选项对应的短选项的字符值,即返回相应的短选项字符.

2.如果解析完最后一个成员将返回1

3.如果getlongopt遇到一个错误的选项,他将打印一个错误消息并返回'?'

4.当get_long解析一个长选项并且发现后面没有参数则返回':',表示缺少参数.

应用举例.

1.一个程序的所需的短选项和长选项如下:

短选项             长选项                         作用
-h                      --help                          输出程序的命令行参数说明并退出
-o filename     --output filename     给定输出文件名
-v                    --version                      显示程序当前的版本后退出 
在这个程序中,首先需要确定两个结构:

1,一个字符串,包含所需要的短选项字符,如果选项后面有参数,字符后面加一个冒号:.本例中,这个字符串应该是:"ho:v",因为-o后面有参数filename,所以要加冒号.

2,一个包含长选项的结构体数组.每个结构体四个域.

第一个域为长选项字符串.第二个域为表示相应的选项是否需要参数,只能为0,没有参数,1,必须要,2,可以要.第三个域决定返回结果类型(即是flags,建议设null),如果为null,则此函数将返回后一个域(即val,一般设置为当前获取的短参数值),否则,此函数将返回0.第四个域为函数的返回值,一般设置为对用的短选项的ascii值.

另外,结构体数组的最后一个元素全部设置为null和0,表示结束.

结构体可以如下所示:

    // 描述了长选项的 struct option 数组
    const struct option long_options[] = 
    {
        { "help", 0, NULL, 'h' },
        { "output", 1, NULL, 'o' },
        { "version", 0, NULL, 'v' },
        { NULL, 0, NULL, 0 }                   // 数组末要求这样一个元素。
    };
以下是程序的代码:

#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

const char* program_name;			//the filename

void print_usage (FILE* stream, int exit_code)	//output message
{
    fprintf (stream, "Usage: %s options [ inputfile ... ]\n", program_name);
    fprintf (
        stream, " -h --help .\n"
        " -o --output filename.\n"
        " -v --version.\n"
            );
    exit (exit_code);
}

// main
int main (int argc, char* argv[])
{
    int next_option;	

    const char* const short_options = "ho:v";    // list of short options
    const struct option long_options[] =         // structure of long options
    {
        { "help", 0, NULL, 'h' },		//no arg 	
        { "output",1, NULL, 'o' },		//must have a arg
        { "version",0, NULL, 'v' },
        { NULL, 0, NULL, 0}             // necessary ele
    };
    
    const char* output_filename = NULL;
    program_name = argv[0];
    
    do
    {
        next_option = getopt_long (argc, argv, short_options, long_options, NULL);
        switch (next_option)
        {
            case 'h':     		//-h or --help, print the info of help 
            print_usage (stdout, 0);
            break;
            case 'o':     		// -o or --output    print the content of the file
            output_filename = optarg;
            execl("/bin/cat","cat",output_filename,NULL);
            break;

            case 'v':   		//-v or    --version
            printf("the version is v1.0\n");
            break;
            case ':':   
            break;
            case '?':  
            print_usage (stderr, 1); // unknow
            break;
            default:                 // else
            print_usage (stderr, 1);
            break;
        }
    }while (next_option !=-1);
    return 0;
}
下面是测试结果:

1.命令行:

./getopt_long_exp -help
Linux 程序设计学习笔记-命令行参数处理
./getopt_long_exp -v

Linux 程序设计学习笔记-命令行参数处理

./getopt_long_exp --version

Linux 程序设计学习笔记-命令行参数处理

./getopt_long_exp --output getopt_long_exp.c

Linux 程序设计学习笔记-命令行参数处理

2.对上面的转换程序的修改:

getopt_long() 函数在 getopt.h 头文件(而非 unistd.h)中,因此将需要将该头文件包含进来。我还包含了 string.h,因为将稍后使用 strcmp() 来帮助确定处理的是哪个长参数。

其他头文件

#include <getopt.h>
#include <string.h>

已经为 --randomize 选项在 globalArgs 中添加了一个标志,并创建了 longOpts 数组来存储关于此程序支持的长选项的信息。除了 --randomize 外,所有的参数都与现有短选项对应(例如,--no-index 等同于 -I)。通过在选项结构中包含其短选项等效项,可以在不向程序添加任何其他代码的情况下处理等效的长选项。

 扩展后的参数

opt = getopt_long( argc, argv, optString, longOpts, &longIndex );
    while( opt != -1 ) {
        switch( opt ) {
            case 'I':
                globalArgs.noIndex = 1; /* true */
                break;
                
            case 'l':
                globalArgs.langCode = optarg;
                break;
                
            case 'o':
                globalArgs.outFileName = optarg;
                break;
                
            case 'v':
                globalArgs.verbosity++;
                break;
                
            case 'h':   /* fall-through is intentional */
            case '?':
                display_usage();
                break;

            case 0:     /* long option without a short arg */
                if( strcmp( "randomize", longOpts[longIndex].name ) == 0 ) {
                    globalArgs.randomized = 1;
                }
                break;
                
            default:
                /* You won't actually get here. */
                break;
        }
        
        opt = getopt_long( argc, argv, optString, longOpts, amp;longIndex );
    }
将 getopt() 调用更改为了 getopt_long(),除了 getopt() 的参数外,它还接受 longOpts 数组和 int 指针 (longIndex)。当getopt_long() 返回 0 时,longIndex 所指向的整数将设置为当前找到的长选项的索引。

新的经改进的选项处理

opt = getopt_long( argc, argv, optString, longOpts, &longIndex );
    while( opt != -1 ) {
        switch( opt ) {
            case 'I':
                globalArgs.noIndex = 1; /* true */
                break;
                
            case 'l':
                globalArgs.langCode = optarg;
                break;
                
            case 'o':
                globalArgs.outFileName = optarg;
                break;
                
            case 'v':
                globalArgs.verbosity++;
                break;
                
            case 'h':   /* fall-through is intentional */
            case '?':
                display_usage();
                break;

            case 0:     /* long option without a short arg */
                if( strcmp( "randomize", longOpts[longIndex].name ) == 0 ) {
                    globalArgs.randomized = 1;
                }
                break;
                
            default:
                /* You won't actually get here. */
                break;
        }
        
        opt = getopt_long( argc, argv, optString, longOpts, amp;longIndex );
    }

我还添加了 0 的 case,以便处理任何不与现有短选项匹配的长选项。在此例中,只有一个长选项,但代码仍然使用 strcmp() 来确保它是预期的那个选项。

这样就全部搞定了;程序现在支持更为详细(对临时用户更加友好)的长选项。

Summarize

UNIX 用户始终依赖于命令行参数来修改程序的行为,特别是那些设计作为小工具集合 (UNIX 外壳环境)的一部分使用的实用工具更是如此。程序需要能够快速处理各个选项和参数,且要求不会浪费开发人员的太多时间。毕竟,几乎没有程序设计为仅处理命令行参数,开发人员更应该将精力放在程序所实际进行的工作上。

getopt() 函数是一个标准库调用,可允许使用直接的 while/switch 语句方便地逐个处理命令行参数和检测选项(带或不带附加的参数)。与其类似的 getopt_long() 允许在几乎不进行额外工作的情况下处理更具描述性的长选项,这非常受开发人员的欢迎。

既然已经知道了如何方便地处理命令行选项,现在就可以集中精力改进程序的命令行,可以添加长选项支持,或添加之前由于不想向程序添加额外的命令行选项处理而搁置的任何其他选项。

不要忘记在某处记录所有的选项和参数,并提供某种类型的内置帮助函数来为健忘的用户提供帮助。


reference:

linux高级程序设计 杨宗德

http://www.ibm.com/developerworks/cn/aix/library/au-unix-getopt.html

转载请注明出处.http://blog.csdn.net/suool/article/details/38089001


The Next:

1. ANSI C文件I/O管理

2. POSIX文件以及目录管理

3. Linux下的一些编码特性和规范

4. Python 两大网络框架
5. network programming.