Linux C/C++ 处理命令行参数

Linux C/C++ 处理命令行参数

argc 和 argv

到目前为止,大部分人编写的所有程序都可以用一个命令运行。举个例子,如果我们编译了一个称之为 myprog 的可执行程序,我们能够在 Linux 命令行使用以下命令运行它:

./myprog

但是,如果你想从命令行向正在运行的程序传递信息,该怎么办?考虑一个更复杂的程序,比如 GCC。要编译 myprog 的可执行文件,我们在命令行输入以下内容:

gcc -o myprog myprog.c

字符串 -o, myprogmyprog.c 都是 gcc 的命令行参数。(从技术上讲,gcc 也是一个参数,我们稍后会看到)

命令行参数非常有用。毕竟,如果不能向 C 函数传递参数,C 函数就不会很有用了——添加向程序传递参数的功能会使它们更有用。事实上,你在命令行上传递的所有参数最终都作为程序中 main 主函数的参数。

到目前为止,我们用于 C 程序的框架大致如下:

#include 

int main() 
{
    return 0;
}

从现在开始,我们的示例可能看起来更像这样:

#include 

int main(int argc, char *argv[]) 
{
    return 0;
}

正如你所看到的,main 函数现在有了参数。变量 argc 代表“参数数量”;argc 包含传递给程序的参数个数。变量 argv 代表“参数向量”。向量(vector)是一维数组,argv 是字符串的一维数组。每个字符串都是传递给程序的参数之一。

例如命令行:

gcc -o myprog myprog.c

将产生 GCC 内部的以下值:

argument value
argc 4
argv[0] gcc
argv[1] -o
argv[2] myprog
argv[3] myprog.c

如你所见,第一个参数(argv[0])是调用程序的名称,在本示例中它是 gcc。因此,程序将始终至少有一个参数,argc 将始终至少为 1

以下程序接受任意数量的命令行参数并将其打印出来:

#include 

int main(int argc, char const *argv[]) {
    printf("argc: %d\n", argc);
    for (int i = 0; i < argc; i++) {
        printf("argv[%d] = %s\n", i, argv[i]);
    }
    return 0;
}

如果你编译你的程序名为 myprog,然后以 ./myprog a b c 的方式调用它,则它会输出以下的打印:

user@hostname:examples$ ./myprog a b c
argc: 4
argv[0] = ./myprog
argv[1] = a
argv[2] = b
argv[3] = c

处理命令行参数

使用自己的程序直接从 argv 向量中提取选项是最容易的,尽管很乏味。

使用标准 C 选项处理函数 getopt 或同一函数的 GNU 增强版本 getopt_long 稍微不那么乏味,它允许 GNU-style 的长选项(例如,--quiet 而不是 -q)。

POSIX (Portable Operating System Interface) 标准,建议使用以下命令行参数约定。

  • 如果命令行参数以连字符(-)开头,则为选项。
  • 如果多个选项不带参数,则它们可以组合在连字符后面。因此,-abc-a -b -c 是相同的。
  • 选项名称是单个字母数字字符。
  • 选项可能需要参数。例如,gcc 命令 -o 选项需要输出文件名。
  • 分隔选项及其参数的空格是可选的。因此,-o fname-ofname 是相同的。
  • 选项通常位于非选项参数之前。(事实上,这一个约定并非是强制性)
  • 参数 -- 终止所有选项;后面的所有命令行参数都被视为非选项参数,即使它们以连字符开头。
  • 选项可以以任何顺序出现,甚至多次出现。其含义由应用程序决定。

此外,GNU 还添加了长选项,如 --help--version 选项。一个长选项以 -- 开头,然后后面跟一串字母数字字符和连字符。选项名称通常为一到三个单词,中间用连字符分隔单词。用户可以缩写选项名称,只要缩写是唯一的。长选项(如 --verbose)通常具有短选项同义词(如 -v)。

可以为长选项指定参数,如下所示:

--option-name=value

不能在选项名称和等号之间或等号和选项值之间键入空格。

--option-name= value        # No
--option-name =value        # No
--option-anme = value       # No

对于程序的命令行选项,最好遵循 POSIX 指南。最简单的办法就是使用 GNU-style 的 getopt_long 函数解析它们。长名称选项的优点之一是它们可以在不同程序之间保持一致。例如,用户期待任何一个程序都有一个 “help” 的选项,帮助他们了解程序的基本用法。

接下来我们了解一下 getopt_long 函数的用法:

#include 

int getopt_long (int argc, char *const argv[], 
                 const char *shortopts,
                 const struct option *longopts, int *longind);

第 1 个与第 2 个参数 argcargvmain 函数一致。

第 3 个参数表示短选项字符串,可以是下列元素:

  • 单个字符,表示选项
  • 单个字符后接一个冒号 : 表示该选项后必须跟一个参数,参数紧跟在选项后或者以空格隔开。该参数的指针赋给 optarg
  • 单个字符后跟两个冒号 :: 表示该选项后可以有参数也可以没有参数。如果有参数,参数必须紧跟在选项后不能以空格隔开。该参数的指针赋给 optarg。(这个特性是 GNU 的扩展)

第 4 个参数指定长选项的结构体 struct option,结构体定义如下:

struct option
{
  const char *name;
  int has_arg;
  int *flag;
  int val;
};
  • name: 长选项的名称。

  • has_arg: 表示选项后面是否携带参数,可以使用 no_argument, required_argumentoptional_argument 三个值:

    • no_argument 选项后面不跟参数,如:--version, --help
    • required_argument 选项需要一个参数,输入格式为:--option name--option=name
    • optional_argument 选项采用可选参数,输入格式为:--option--option=name

    在头文件 中这三个值的定义如下:

    /* Names for the values of the 'has_arg' field of 'struct option'.  */
    #define no_argument         0
    #define required_argument   1
    #define optional_argument   2
    
  • flag: 该字段有两种含义,空或者非空:

    • 如果为空(NULL),getopt_long 返回 val 值。
    • 如果不为空,则 getopt_long 返回 0,val 赋值给 flag 指针所指向的变量。
  • val: 表示指定函数找到该选项时的返回值。

第 5 个参数 longind 参数一般赋为 NULL 即可;如果没有设置为 NULL,那么它就指向一个变量,这个变量会被赋值为寻找到的长选项在 longopts 中的索引值,这可以用于错误诊断。

getopt_long 用法

以下代码演示了 getopt_long 的基本用法:

hello.c

#include       // included for `printf`
#include      // included for `getopt_long`
#include      // included for `basename`
#include      // included for `EXIT_SUCCESS|EXIT_FAILURE`

#define VERSION "1.0.0"

// define option table
static const struct option longopts[] = {
    {"help", no_argument, NULL, 'h'},
    {"version", no_argument, NULL, 'V'},
    // TODO::
    {"greeting", required_argument, NULL, 'g'},
    {"next-generation", no_argument, NULL, 'n'},
    {"traditional", no_argument, NULL, 't'},
    // ----------
    {NULL, 0, NULL, 0}};

// Print help info.
static void print_help(const char *progname) {
    printf("Usage: %s [OPTION]...\n", progname);
    printf("Options:\n");
    printf("  -h, --help              display this help\n");
    printf("  -V, --version           display version information\n");
    // TODO::
    printf("  -g, --greeting=TEXT     use TEXT as the greeting message\n");
    printf("  -t, --traditional       use traditional greeting format\n");
    printf("  -n, --next-generation   use next-generation greeting format\n");
    // ----------
}

static void print_version() {
    printf("%s\n", VERSION);
}

int main(int argc, char *argv[])
{
    int optc;
    const char *program_name = basename(argv[0]);
    int lose = 0;

    // TODO::
    int t = 0, n = 0;
    const char *greeting = NULL;
    // ----------

    while ((optc = getopt_long(argc, argv, "hVg:nt", longopts, NULL)) != -1)
        switch (optc) {
            /* One goal here is having --help and --version exit immediately,
               per GNU coding standards.  */
            case 'h':
                print_help(program_name);
                exit(EXIT_SUCCESS);
                break;
            case 'V':
                print_version();
                exit(EXIT_SUCCESS);
                break;
            case 'g':
                greeting = optarg;
                break;
            case 'n':
                n = 1;
                break;
            case 't':
                t = 1;
                break;
            default:
                lose = 1;
                break;
        }

    if (lose || optind < argc) {
        /* Print error message and exit.  */
        if (optind < argc)
            fprintf(stderr, "%s: extra operand: %s\n", program_name,
                    argv[optind]);
        fprintf(stderr, "Try `%s --help' for more information.\n",
                program_name);
        exit(EXIT_FAILURE);
    }

    if (t) {
        printf("hello, world\n");
    } else if (n) {
        printf("+---------------+\n");
        printf("| Hello, world! |\n");
        printf("+---------------+\n");
    } else {
        if (!greeting) greeting = "Hello, world!";
        puts(greeting);
    }
    return 0;
}
  1. 定义长选项 struct option 结构体数组 longopts

    • 按照需要填充 longopts 数组
    • longopts 的最后一个元素必须是全 0 填充,否则会报段错误
  2. 调用 getopt_long 函数解析命令行参数

  3. 遵循 GNU Coding Standards 规范,提供 --version--help 两个标准选项

参考资料

  1. argc and argv
  2. Standards for Command Line Interfaces

延展阅读

△ \triangle C/C++读写二进制文件
▽ \bigtriangledown Linux C/C++ 单实例进程设计

你可能感兴趣的:(Program,linux,c语言,c++)