【C语言】C语言如何通过命令行参数提升程序运行灵活性

本文详细介绍了如何在C语言中通过命令行参数和getopt函数提高程序的灵活性,包括手动解析命令行参数和使用getopt库函数(如getopt、getopt_long和getopt_long_only)来解析短选项和长参数。作者强调了命令行参数在程序易用性和扩展性方面的价值。
摘要由CSDN通过智能技术生成

🧑 作者简介:阿里巴巴嵌入式技术专家,深耕嵌入式+人工智能领域,具备多年的嵌入式硬件产品研发管理经验。

📒 博客介绍:分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向的学习指导、简历面试辅导、技术架构设计优化、开发外包等服务,有需要可私信联系。

1. 概述

gcc helloworld.c -o helloworld

这条命令,大家想必都非常熟悉了,就是使用gcc把c语言源代码helloworld.c编译为可执行程序helloworld。有没有想过,gcc这个命令内部是如何识别到我们传递给他的源文件路径以及-o参数和目标文件路径的呢?带着这个问题,我们开始今天的注意。

在C语言编程中,命令行参数是一种强大的工具,可以极大地提升程序的运行灵活性。通过命令行参数,用户可以轻松地为程序的每次运行提供不同的输入数据、配置选项或其他必要的运行参数,而无需修改程序源代码。

本文将探讨如何使用C语言处理命令行参数,并通过这种方式增加程序的灵活性和实用性。

2. 如何传参给程序

假设让你用C语言写个代码,功能是用来读取并显示某文件的内容,那我们会怎么写代码呢?最初级的写法是不是就是用fopen()函数打开这个文件,用fread()函数读取文件内容,然后用printf()函数把读到的内容打印出来,最后再调用fclose()关闭文件。这个做法有没有问题?没问题。

如果现在让你再显示另外一个文件的内容怎么办?So Easy,修改代码里传递给fopen()函数的路径,重新编译运行。有没有问题?完美,也没问题。

好,需求又变了,现在给你1000个文件,让你显示它们的内容怎么办?修改1000遍代码,编译1000个程序,运行1000次吗?有没有问题?问题来了,改代码要改到吐血了,简直不是人干的活。。。

在软件编程这个行业里做久了,会发现当某一段时间,你在做某个重复的工作时,一定是有办法去优化它的,不管是流程梳理还是制作工具辅助提效还是使用更先进高效的方法。

在C语言中,可以通过int main(int argc, char *argv[])函数的参数来访问命令行参数。main()函数通常有两个参数:argcargv。我们在给程序运行添加的参数就是通过argcargv的组合传递给main()函数的。其中:

  1. argc是一个整数,表示命令行参数的数量(包括运行的程序本身)
  2. argv是一个指向字符串数组的指针,每个字符串代表的就是一个命令行参数。argv[0] 是程序的名称,argv[1]argv[2]一直到argv[argc - 1]等就是程序的所有参数。
//文件名:test_arg.c
#include <stdio.h>

int main(int argc, char *argv[])
{
    printf("当前运行的程序: %s\n", argv[0]);
    printf("参数个数(包含程序本身)共计%d个\n", argc);
    
    printf("参数列表:\n");
    for (int i = 1; i < argc; i++) {
        printf("  第%d个: %s\n", i, argv[i]);
    }
    
    return 0;
}

如下图所示,我们可以运行上述代码来获取所有参数信息:

  • 当没有参数时,argc为1,argv[0]就是程序运行的路径
  • 有参数,程序使用相对路径时,可以得到所有参数及其个数,此时argv[0]就是程序的相对路径。
  • 有参数,程序使用绝对路径时,可以得到所有参数及其个数,此时argv[0]就是程序的绝对路径。
    遍历命令行参数

3. 手动解析命令行参数

我们能够访问命令行参数之后,下一步就是处理它们了。

我们摘录一段跨平台多媒体开发库SDL的demo程序中解析命令行参数的代码,看看开源软件是如何解析它们的。效果比较好;

已知这段程序的命令行参数方式:app [-width N] [-height N] [-bpp N] [-warp] [-hw] [-fullscreen],其解析源码如下所示:

int main(int argc, char *argv[])
{
	。。。
	
	int videoflags = SDL_SWSURFACE;
	int video_bpp = 16;
	
	for(i = 1; argv[i]; ++i) {
	    if( strcmp(argv[i], "-bpp") == 0){
	        video_bpp = atoi(argv[++i]);
	        if(video_bpp<=8){
	            video_bpp=16;
	            fprintf(stderr, "forced 16 bpp mode\n");
	        }
	    }
	    else if(strcmp(argv[i], "-hw") == 0){
	        videoflags |= SDL_HWSURFACE;
	    } 
	    else if(strcmp(argv[i], "-warp") == 0){
	        videoflags |= SDL_HWPALETTE;
	    }
	    else if(strcmp(argv[i], "-width") == 0 && argv[i+1]){
	        w = atoi(argv[++i]);
	    } 
	    else if(strcmp(argv[i], "-height") == 0 && argv[i+1]){
	        h = atoi(argv[++i]);
	    } 
	    else if(strcmp(argv[i], "-resize") == 0){
	        videoflags |= SDL_RESIZABLE;
	    } 
	    else if(strcmp(argv[i], "-noframe") == 0){
	        videoflags |= SDL_NOFRAME;
	    } 
	    else if(strcmp(argv[i], "-fullscreen") == 0){
	        videoflags |= SDL_FULLSCREEN;
	    } 
	    else {
	        fprintf(stderr, 
	                "Usage: %s [-width N] [-height N] [-bpp N] [-warp] [-hw] [-fullscreen]\n",
	                argv[0]);
	        quit(1);
	    }
	}
}

上面这段代码演示如何通过for循环来遍历命令行参数并进行解析的全过程,而且不用考虑命令行的参数顺序。通过这种方式,你可以轻松地扩展程序的功能,而无需修改源代码。大家可以收藏保存,有相关需求时拿过来修修改改就可以用了。

4. 使用getopt类库函数来解析

在C语言中,处理命令行参数,除了直接使用main函数的argcargv参数外,还可以使用C库函数中的getopt()getopt_long()getopt_long_only()函数来解析,这些C库函数提供了更加灵活的参数解析方法。

4.1 getopt解析短选项

4.1.1 函数声明

#include <unistd.h>

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

extern char *optarg;
extern int optind, opterr, optopt;

4.1.2 参数解析

  • argc:命令行参数的数量,通常是main函数的第一个参数。
  • argv:命令行参数的数组,通常是main函数的第二个参数。
  • optstring:一个字符串,包含了所有有效选项字符的列表。如果某个选项后面需要跟一个额外的参数值,那么在optstring中该选项字符后面应该加上一个冒号:,否则也不需要。假设某命令参数:-v -f filename -n num-v后面不需要跟额外参数,-f后面跟一个额外的参数filename-n后面跟一个额外的参数num,那么此时optstring就是vf:n:

getopt函数的工作原理是遍历argv数组,检查每个参数是否是一个选项。如果是选项,则根据optstring来确定如何处理该选项。

4.1.3 返回值

  • 如果成功解析一个选项,getopt()返回该选项字符。
  • 如果遇到一个非选项参数(即不以 - 开头的参数),getopt()返回-1,并且该参数之后的参数可以通过argv数组直接访问。
  • 如果遇到一个未知选项,getopt()返回?,并且全局变量optopt被设置为该未知选项字符。
  • 如果遇到一个需要参数的选项,但后面没有跟着参数值,getopt()会返回:,并且全局变量optopt被设置为该选项字符。

4.1.4 涉及的全局变量

  • optarg:当getopt遇到一个需要参数的选项时,optarg会被设置为该选项的参数值。如果选项不需要参数,或者遇到未知选项,optarg的值是不确定的。
  • optind:这是一个索引,指向argv数组中下一个要处理的元素。通常,在getopt() 返回 -1之后,你可以使用optind来获取非选项参数。
  • optopt:当getopt()遇到一个未知选项时,optopt会被设置为该未知选项字符。这可以用于错误处理,以确定是哪个选项导致了问题。

4.1.5 示例代码

下面是一个使用 getopt 的简单例子:

//文件名:test_getopt.c 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int opt;
    int verbose = 0;
    char *filename = NULL;
  	int num = 0;

    while ((opt = getopt(argc, argv, "vf:n:")) != -1) {
        switch (opt) {
        case 'v':
            verbose = 1;
            break;
        case 'f':
            filename = optarg;
            break;
        case 'n':
            num= atoi(optarg);  
            break;
        case ':':
            fprintf(stderr, "Option -%c requires an argument.\n", optopt);
            exit(EXIT_FAILURE);
        case '?':
            fprintf(stderr, "Unknown option `-%c'.\n", optopt);
            exit(EXIT_FAILURE);
        default:
            abort();
        }
    }

    // optind 指向下一个要处理的 argv 元素,此处可以将所有未处理的(非选项)参数遍历打印出来
    if (optind < argc) {
        printf("Non-option arguments:\n");
        while (optind < argc) {
            printf("argv[%d] = %s\n", optind, argv[optind++]);
        }
    }

    if (verbose) {
        printf("Verbose mode is on.\n");
    }

    if (filename) {
        printf("File to process: %s\n", filename);
    }
    
    if (num > 0) {
        printf("Num: %d\n", num);
    }

    return 0;
}

test_getopt.c编译为可执行程序test_getopt,然后分不同情况运行查看效果:

  • 不带任何参数
$ ./test_getopt
  • 所有参数都是有效(可识别)参数:-v -f ./config.log -n 2
$ ./test_getopt -v -f ./config.log -n 2
Verbose mode is on.
File to process: ./config.log
Num: 2
  • 存在未知选项参数:-h haha
$ ./test_getopt -v -f ./config.log -n 2 -h haha
./test_getopt: invalid option -- 'h'
Unknown option `-h'.
  • 需要参数的选项,但后面没有跟着参数值:-n
$ ./test_getopt -v -f ./config.log -n
./test_getopt: option requires an argument -- 'n'
Unknown option `-n'.
  • 存在非选项(即不以-开头的)参数:in val id
$ ./test_getopt in -v val -f ./config.log -n 2 id
Non-option arguments:
argv[7] = in
argv[8] = val
argv[9] = id
Verbose mode is on.
File to process: ./config.log
Num: 2

4.2 getopt_long解析长参数

完善中

4.3 getopt_long_only解析参数

完善中

5. 思考题

  1. 已知,Linux中ls -l -als -la的效果是一样的。经过前面的学习,我们已经知道了如何解析ls -l -a格式的命令行参数,那么ls -la格式的参数该怎么解析呢?
  2. 现在再让你用C语言写个代码,用来实现读取并显示某文件的内容,那你会怎么写代码呢?

欢迎大家在评论区留下你的思考和答案。

6. 总结

通过命令行参数,应用程序可以获得更高的灵活性和实用性。通过合理地处理命令行参数,你可以让用户轻松地为程序提供输入数据、配置选项或其他必要的运行参数。这不仅提高了程序的灵活性,还使得程序更加易于使用和扩展。结合适当的错误处理和命令帮助,可以创建出既强大又易于使用的命令行工具。

最后,再补充一点,如果程序本身的命令行参数比较简单,比如就一两个参数或者说只是传递一个配置文件路径,那其实没有使用**getopt*()**函数来解析,代码里使用strcmp()函数简单几行代码做个字符串匹配就可以了。工具诞生的本意是服务于人,把人从繁重的工作中解放出来。谨记以实用为主,切勿杀鸡用牛刀。

  • 20
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

I'mAlex

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值