glibc 知:手册25:程序基础/系统接口

本文详细介绍了程序参数解析的多种方法,包括使用getopt和argp函数。getopt用于处理典型的Unix命令行选项,而argp提供了一种更高级的接口,可以自动生成帮助和版本输出,处理长选项,以及组合多个解析器。此外,还讨论了环境变量、辅助向量和系统调用等概念。

1. 前言

The GNU C Library Reference Manual for version 2.35

2. 基本程序/系统接口

The Basic Program/System Interface

进程是分配系统资源的原始单位。每个进程都有自己的地址空间和(通常)一个控制线程。一个进程执行一个程序;您可以让多个进程执行同一个程序,但每个进程在其自己的地址空间内都有自己的程序副本,并独立于其他副本执行它。尽管它可能在同一个程序中有多个控制线程,并且一个程序可能由多个逻辑上独立的模块组成,但一个进程总是只执行一个程序。

请注意,出于本手册的目的,我们使用“程序”的特定定义,它对应于 Unix 系统上下文中的通用定义。在流行的用法中,“程序”的定义要广泛得多。例如,它可以指代系统的内核、编辑器宏、复杂的软件包或在进程中执行的离散代码部分。

编写程序是本手册的全部内容。本章解释了你的程序和运行或调用它的系统之间最基本的接口。这包括从系统传递参数(参数和环境),从系统请求基本服务,以及告诉系统程序完成。

一个程序使用 exec 系列系统调用启动另一个程序。本章从被执行人的角度来看程序启动。要从执行者的角度查看事件,请参阅执行文件

2.1. 程序参数

Program Arguments

系统通过调用函数 main 来启动一个 C 程序。编写一个名为 main 的函数由您自己决定——否则,您甚至无法无错误地链接您的程序。

在 ISO C 中,您可以将 main 定义为不带参数,或者带两个参数来表示程序的命令行参数,如下所示:

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

命令行参数是用于调用程序的 shell 命令中给出的以空格分隔的标记;因此,在“cat foo bar”中,参数是“foo”和“bar”。程序查看其命令行参数的唯一方法是通过 main 的参数。如果 main 不带参数,那么您将无法进入命令行。

argc 参数的值是命令行参数的数量。argv 参数是 C 字符串的向量;它的元素是单独的命令行参数字符串。正在运行的程序的文件名也作为第一个元素包含在向量中;argc 的值计算这个元素。空指针总是跟在最后一个元素后面:argv[argc] 就是这个空指针。

对于命令“cat foo bar”,argc 为 3,而 argv 包含三个元素,“cat”、“foo”和“bar”。

在 Unix 系统中,您可以定义 main 第三种方式,使用三个参数:

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

前两个参数是一样的。第三个参数 envp 给出了程序的环境;它与environ的值相同。请参阅环境变量。POSIX.1 不允许这种三参数形式,因此为了便于移植,最好编写 main 带两个参数,并使用 environ 的值。

2.1.1. 程序参数语法约定

Program Argument Syntax Conventions

POSIX 建议将这些约定用于命令行参数。getopt(请参阅使用 getopt 解析程序选项)和 argp_parse(请参阅使用 Argp 解析程序选项)使实现它们变得容易。

  • 如果参数以连字符分隔符 (‘-’) 开头,则参数是选项。

  • 如果选项不带参数,则多个选项可以在单个标记中跟随连字符分隔符。因此,“-abc”等价于“-a -b -c”。

  • 选项名称是单个字母数字字符(对于 isalnum;请参阅字符分类)。

  • 某些选项需要参数。例如,ld 命令的 -o 选项需要一个参数——一个输出文件名。

  • 选项及其参数可能会或可能不会显示为单独的标记。(换句话说,分隔它们的空格是可选的。)因此,-o foo 和 -ofoo 是等价的。

  • 选项通常在其他非选项参数之前。

    GNU C 库中 getopt 和 argp_parse 的实现通常使它看起来好像所有选项参数都在所有非选项参数之前指定以进行解析,即使您的程序的用户混合了选项和非选项参数.他们通过重新排序 argv 数组的元素来做到这一点。这种行为是非标准的;如果要抑制它,请定义 _POSIX_OPTION_ORDER 环境变量。请参阅标准环境变量

  • 参数 – 终止所有选项;任何后续参数都被视为非选项参数,即使它们以连字符开头。

  • 由单个连字符组成的标记被解释为普通的非选项参数。按照惯例,它用于指定来自标准输入和输出流的输入或输出。

  • 选项可以按任何顺序提供,也可以多次出现。解释由特定的应用程序决定。

GNU 为这些约定添加了长选项。长选项包括 – 后跟由字母数字字符和破折号组成的名称。选项名称通常为一到三个单词,并用连字符分隔单词。只要缩写是唯一的,用户就可以缩写选项名称。

要为长选项指定参数,请编写 --name=value。此语法允许长选项接受本身是可选的参数。

最终,GNU 系统将为 shell 中的长选项名称提供补全。

2.1.2. 解析程序参数

Parsing Program Arguments

如果程序的命令行参数的语法足够简单,您可以简单地手动从 argv 中挑选参数。但是除非您的程序采用固定数量的参数,或者所有参数都以相同的方式解释(例如文件名),否则您通常最好使用 getopt(请参阅使用 getopt 解析程序选项)或 argp_parse(请参阅使用 Argp 解析程序选项)进行解析。

getopt 更标准(它的短选项版本是 POSIX 标准的一部分),但是对于非常简单和非常复杂的选项结构,使用 argp_parse 通常更容易,因为它为您做了更多的脏工作。

2.2. 使用 getopt 解析程序选项

Parsing program options using getopt

getopt 和 getopt_long 函数自动完成解析典型 unix 命令行选项所涉及的一些琐事。

2.2.1. 使用 getopt 函数

Using the getopt function

以下是有关如何调用 getopt 函数的详细信息。要使用此功能,您的程序必须包含头文件 unistd.h。

变量:int opterr

如果此变量的值非零,则 getopt 会在遇到未知选项字符或缺少必需参数的选项时将错误消息打印到标准错误流。这是默认行为。如果将此变量设置为零,getopt 不会打印任何消息,但仍会返回字符 ?指示错误。

变量:int optopt

当 getopt 遇到未知选项字符或缺少必需参数的选项时,它将该选项字符存储在此变量中。您可以使用它来提供您自己的诊断消息。

变量:int optind

此变量由 getopt 设置为要处理的 argv 数组的下一个元素的索引。一旦 getopt 找到所有选项参数,您就可以使用此变量来确定其余非选项参数的开始位置。该变量的初始值为 1。

变量:char * optarg

对于那些接受参数的选项,此变量由 getopt 设置为指向选项参数的值。

函数:int getopt (int argc, char *const *argv, const char *options)

Preliminary: | MT-Unsafe race:getopt env | AS-Unsafe heap i18n lock corrupt | AC-Unsafe mem lock corrupt | See POSIX Safety Concepts.

getopt 函数从 argv 和 argc 参数指定的参数列表中获取下一个选项参数。通常这些值直接来自 main 接收到的参数。

options 参数是一个字符串,它指定对该程序有效的选项字符。此字符串中的选项字符后面可以跟一个冒号(‘:’),表示它需要一个必需的参数。如果选项字符后跟两个冒号(‘::’),则其参数是可选的;这是一个 GNU 扩展。

getopt 有三种方法来处理非选项 argv 元素之后的选项。特殊参数“–”在所有情况下都强制结束选项扫描。

  • 默认设置是在扫描时置换 argv 的内容,以便最终所有非选项都位于末尾。这允许以任何顺序给出选项,即使是编写的程序没有预料到这一点。
  • 如果选项参数字符串以连字符 (‘-’) 开头,则对其进行特殊处理。它允许返回不是选项的参数,就好像它们与选项字符’\1’相关联。
  • POSIX 要求以下行为:第一个非选项停止选项处理。通过设置环境变量 POSIXLY_CORRECT 或以加号 (‘+’) 开头的选项参数字符串来选择此模式。

getopt 函数返回下一个命令行选项的选项字符。当没有更多的选项参数可用时,它返回 -1。可能还有更多的非选项参数;您必须将外部变量 optind 与 argc 参数进行比较以进行检查。

如果选项有参数,getopt 会通过将参数存储在变量 optarg 中来返回参数。您通常不需要复制 optarg 字符串,因为它是指向原始 argv 数组的指针,而不是指向可能被覆盖的静态区域的指针。

如果 getopt 在 argv 中发现未包含在选项中的选项字符,或者缺少选项参数,它会返回“?”并将外部变量 optopt 设置为实际的选项字符。如果选项的第一个字符是冒号(‘:’),则 getopt 返回‘:’而不是‘?’以指示缺少选项参数。此外,如果外部变量 opterr 不为零(这是默认值),getopt 会打印一条错误消息。

2.2.2. 使用 getopt 解析参数的示例

Example of Parsing Arguments with getopt

下面是一个示例,展示了通常如何使用 getopt。需要注意的关键点是:

  • 通常,getopt 在循环中被调用。当 getopt 返回 -1 表示没有更多选项存在时,循环终止。
  • switch 语句用于调度 getopt 的返回值。在典型使用中,每种情况只设置一个稍后在程序中使用的变量。
  • 第二个循环用于处理剩余的非选项参数。
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int
main (int argc, char **argv)
{
   
   
  int aflag = 0;
  int bflag = 0;
  char *cvalue = NULL;
  int index;
  int c;

  opterr = 0;

  while ((c = getopt (argc, argv, "abc:")) != -1)
    switch (c)
      {
   
   
      case 'a':
        aflag = 1;
        break;
      case 'b':
        bflag = 1;
        break;
      case 'c':
        cvalue = optarg;
        break;
      case '?':
        if (optopt == 'c')
          fprintf (stderr, "Option -%c requires an argument.\n", optopt);
        else if (isprint (optopt))
          fprintf (stderr, "Unknown option `-%c'.\n", optopt);
        else
          fprintf (stderr,
                   "Unknown option character `\\x%x'.\n",
                   optopt);
        return 1;
      default:
        abort ();
      }

  printf ("aflag = %d, bflag = %d, cvalue = %s\n",
          aflag, bflag, cvalue);

  for (index = optind; index < argc; index++)
    printf ("Non-option argument %s\n", argv[index]);
  return 0;
}

以下是一些示例,展示了该程序使用不同的参数组合打印的内容:

% testopt
aflag = 0, bflag = 0, cvalue = (null)

% testopt -a -b
aflag = 1, bflag = 1, cvalue = (null)

% testopt -ab
aflag = 1, bflag = 1, cvalue = (null)

% testopt -c foo
aflag = 0, bflag = 0, cvalue = foo

% testopt -cfoo
aflag = 0, bflag = 0, cvalue = foo

% testopt arg1
aflag = 0, bflag = 0, cvalue = (null)
Non-option argument arg1

% testopt -a arg1
aflag = 1, bflag = 0, cvalue = (null)
Non-option argument arg1

% testopt -c foo arg1
aflag = 0, bflag = 0, cvalue = foo
Non-option argument arg1

% testopt -a -- -b
aflag = 1, bflag = 0, cvalue = (null)
Non-option argument -b

% testopt -a -
aflag = 1, bflag = 0, cvalue = (null)
Non-option argument -

2.2.3. 使用 getopt_long 解析长选项

Parsing Long Options with getopt_long

要接受 GNU 样式的长选项以及单字符选项,请使用 getopt_long 而不是 getopt。此函数在 getopt.h 中声明,而不是在 unistd.h 中。如果每个程序使用任何选项,您应该让每个程序都接受长选项,因为这几乎不需要额外的工作,并且可以帮助初学者记住如何使用该程序。

数据类型:struct option

为了 getopt_long,此结构描述了一个长选项名称。参数 longopts 必须是这些结构的数组,每个长选项一个。使用包含全零的元素终止数组。

struct 选项结构具有以下字段:

const char *name

该字段是选项的名称。它是一个字符串。

int has_arg

该字段说明选项是否接受参数。它是一个整数,存在三个合法值:no_argument、required_argument 和 optional_argument。

int *flag

int val

这些字段控制在选项发生时如何报告或处理选项。

如果 flag 是空指针,则 val 是标识此选项的值。通常选择这些值来唯一标识特定的多头期权。

如果 flag 不是空指针,它应该是一个 int 变量的地址,它是这个选项的标志。val 中的值是存储在标志中的值,以指示看到该选项。

函数:int getopt_long (int argc, char *const *argv, const char *shortopts, const struct option *longopts, int *indexptr)

Preliminary: | MT-Unsafe race:getopt env | AS-Unsafe heap i18n lock corrupt | AC-Unsafe mem lock corrupt | See POSIX Safety Concepts.

从向量 argv(长度为 argc)解码选项。参数 shortopts 描述了要接受的短选项,就像它在 getopt 中所做的那样。参数 longopts 描述了接受的长选项(见上文)。

当 getopt_long 遇到一个短选项时,它会做与 getopt 相同的事情:它返回选项的字符代码,并将选项的参数(如果有的话)存储在 optarg 中。

当 getopt_long 遇到 long 选项时,它会根据该选项定义的 flag 和 val 字段执行操作。

如果 flag 是空指针,则 getopt_long 返回 val 的内容以指示它找到了哪个选项。您应该在 val 字段中为具有不同含义的选项安排不同的值,以便您可以在 getopt_long 返回后解码这些值。如果 long 选项等价于 short 选项,则可以在 val 中使用 short 选项的字符代码。

如果 flag 不是空指针,这意味着这个选项应该只在程序中设置一个标志。该标志是您定义的 int 类型的变量。将标志的地址放在标志字段中。在 val 字段中输入您希望此选项存储在标志中的值。在这种情况下,getopt_long 返回 0。

对于任何长选项,getopt_long 会告诉您选项定义的数组 longopts 中的索引,方法是将其存储到 *indexptr 中。您可以使用 longopts[*indexptr].name 获取选项的名称。因此,您可以通过 val 字段中的值或索引来区分长选项。您还可以通过这种方式区分设置标志的长选项。

当 long 选项有参数时,getopt_long 在返回之前将参数值放入变量 optarg 中。当该选项没有参数时,Optarg中的值是一个空指针。这是您如何判断是否提供了可选参数的方法。

当 getopt_long 没有更多选项要处理时,它返回 -1,并在变量 optind 中保留下一个剩余参数的 argv 索引。

由于在 getopt_long 发明之前使用了长选项名称,因此程序接口要求程序识别诸如“-option value”而不是“–option value”之类的选项。为了使这些程序能够使用 GNU getopt 功能,还有一个可用的功能。

函数:int getopt_long_only (int argc, char *const *argv, const char *shortopts, const struct option *longopts, int *indexptr)

Preliminary: | MT-Unsafe race:getopt env | AS-Unsafe heap i18n lock corrupt | AC-Unsafe mem lock corrupt | See POSIX Safety Concepts.

getopt_long_only 函数等价于 getopt_long 函数,但它允许应用程序的用户只使用“-”而不是“–”来传递长选项。‘–’ 前缀仍然可以识别,但如果看到“-”,则不会查看短选项,而是首先尝试此参数是否命名为长选项。如果不是,则将其解析为短选项。

假设使用 getopt_long_only 启动应用程序

  app -foo

getopt_long_only 将首先查找名为“foo”的长选项。如果未找到,则会识别短选项“f”、“o”和“o”。

2.2.4. 使用 getopt_long 解析长选项的示例

Example of Parsing Long Options with getopt_long

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

/* Flag set by ‘--verbose’. */
static int verbose_flag;

int
main (int argc, char **argv)
{
   
   
  int c;

  while (1)
    {
   
   
      static struct option long_options[] =
        {
   
   
          /* These options set a flag. */
          {
   
   "verbose", no_argument,       &verbose_flag, 1},
          {
   
   "brief",   no_argument,       &verbose_flag, 0},
          /* These options don’t set a flag.
             We distinguish them by their indices. */
          {
   
   "add",     no_argument,       0, 'a'},
          {
   
   "append",  no_argument,       0, 'b'},
          {
   
   "delete",  required_argument, 0, 'd'},
          {
   
   "create",  required_argument, 0, 'c'},
          {
   
   "file",    required_argument, 0, 'f'},
          {
   
   0, 0, 0, 0}
        };
      /* getopt_long stores the option index here. */
      int option_index = 0;

      c = getopt_long (argc, argv, "abc:d:f:",
                       long_options, &option_index);

      /* Detect the end of the options. */
      if (c == -1)
        break;

      switch (c)
        {
   
   
        case 0:
          /* If this option set a flag, do nothing else now. */
          if (long_options[option_index].flag != 0)
            break;
          printf ("option %s", long_options[option_index].name);
          if (optarg)
            printf (" with arg %s", optarg);
          printf ("\n");
          break
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

canpool

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

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

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

打赏作者

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

抵扣说明:

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

余额充值