在 C 中使用 getopt 进行短选项解析

通过允许用户告诉程序要做什么,使用命令行使你的程序更灵活。
52 位读者喜欢这篇文章。
Why and how to handle exceptions in Python Flask

图片来自 Unsplash.com,知识共享零许可 

当你已经知道要操作哪些文件以及要采取哪些操作时,编写 C 程序来处理文件很容易。 如果你将文件名“硬编码”到程序中,或者如果你的程序被编码为仅以一种方式执行操作,那么你的程序将始终知道该做什么。

但是,如果你的程序可以在每次运行时响应用户,则可以使你的程序 更加灵活。 让你的用户告诉你的程序要使用哪些文件或如何以不同的方式执行操作。 为此,你需要读取命令行。

读取命令行

当你在 C 中编写程序时,你可能会从声明开始

int main()

这是启动 C 程序的最简单方法。 但是,如果在括号中添加这些标准参数,你的程序可以读取在命令行中给出的选项

int main(int argc, char **argv)

argc 变量是参数计数或命令行上参数的数量。 这将始终是一个至少为一的数字。

argv 变量是一个双指针,一个字符串数组,其中包含来自命令行的参数。 数组中的第一个条目 *argv[0] 始终是程序的名称。 **argv 数组的其他元素包含其余的命令行参数。

我将编写一个简单的程序来回显在命令行中给它的选项。 这类似于 Linux echo 命令,但它也打印程序的名称。 它还使用 puts 函数在自己的行上打印每个命令行选项

#include <stdio.h>

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

  printf("argc=%d\n", argc); /* debugging */

  for (i = 0; i < argc; i++) {
    puts(argv[i]);
  }

  return 0;
}

编译此程序并使用一些命令行选项运行它,你将看到你的命令行打印回给你,每个项目都在自己的行上

$ ./echo this program can read the command line
argc=8
./echo
this
program
can
read
the
command
line

此命令行将程序的 argc 设置为 8,并且 **argv 数组包含八个条目:程序的名称,加上用户输入的七个单词。 与 C 程序中一样,数组从零开始,因此元素的编号为 0、1、2、3、4、5、6、7。 这就是为什么你可以使用比较 i < argcfor 循环处理命令行的原因。

你可以使用它来编写你自己的 Linux catcp 命令版本。 cat 命令的基本功能是显示一个或多个文件的内容。 这是一个简单的 cat 版本,它从命令行读取文件名

#include <stdio.h>

void
copyfile(FILE *in, FILE *out)
{
  int ch;

  while ((ch = fgetc(in)) != EOF) {
    fputc(ch, out);
  }
}

int
main(int argc, char **argv)
{
  int i;
  FILE *fileptr;

  for (i = 1; i < argc; i++) {
    fileptr = fopen(argv[i], "r");

    if (fileptr != NULL) {
      copyfile(fileptr, stdout);
      fclose(fileptr);
    }
  }

  return 0;
}

这个简单的 cat 版本从命令行读取文件名列表,并将每个文件的内容一次一个字符地显示到标准输出。 例如,如果我有一个名为 hello.txt 的文件,其中包含几行文本,我可以使用我自己的 cat 命令显示其内容

$ ./cat hello.txt 
Hi there!
This is a sample text file.

使用此示例程序作为起点,你可以通过仅读取两个文件名来编写你自己的其他 Linux 命令版本,例如 cp 程序:一个要读取的文件和另一个要写入复制的文件。

读取命令行选项

从命令行读取文件名和其他文本很棒,但是如果你希望你的程序根据用户给它的 选项 来更改其行为怎么办? 例如,Linux cat 命令支持多个命令行选项,包括

  • -b 在非空白行旁边放置行号
  • -E 将行尾显示为 $
  • -n 在所有行旁边放置行号
  • -s 抑制打印重复的空白行
  • -T 将制表符显示为 ^I
  • -v 详细模式;使用 ^xM-x 表示法显示非打印字符,但换行符和制表符除外

这些单字母选项称为短选项,它们总是以单个连字符开头。 你通常会看到这些短选项单独编写,例如 cat -E -n,但你也可以将短选项组合成单个选项字符串,例如 cat -En

幸运的是,有一种简单的方法可以从命令行读取这些选项。 所有 Linux 和 Unix 系统都包含一个特殊的 C 库,名为 getopt,它在 unistd.h 头文件中定义。 你可以在你的程序中使用 getopt 来读取这些短选项。

与其他 Unix 系统不同,Linux 上的 getopt 将始终确保你的短选项出现在命令行的前面。 例如,假设用户输入 cat -E file -n-E 选项在前,但 -n 选项在文件名之后。 但是,如果你使用 Linux getopt,你的程序将始终表现得好像用户输入了 cat -E -n file。 这使得处理变得轻而易举,因为 getopt 可以解析短选项,从而在命令行上为你留下一系列文件名,你的程序可以使用 **argv 数组读取这些文件名。

你可以像这样使用 getopt

       #include <unistd.h>

       int getopt(int argc, char **argv, char *optstring);

选项字符串 optstring 包含有效选项字符的列表。 如果你的程序只允许 -E-n 选项,则可以使用 “En” 作为你的选项字符串。

你通常在循环中使用 getopt 来解析命令行的选项。 在每次 getopt 调用时,该函数返回它在命令行上找到的下一个短选项,或者对于任何无法识别的短选项,返回 '?' 值。 当 getopt 找不到更多短选项时,它返回 -1 并将全局变量 optind 设置为 **argv 中所有短选项之后的下一个元素。

让我们看一个简单的例子。 这个演示程序不是 cat 及其所有选项的完整替代品,但它可以解析其命令行。 每次找到有效的命令行选项时,它都会打印一条简短的消息来指示已找到它。 在你自己的程序中,你可能改为设置一个变量或采取一些其他操作来响应该命令行选项

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

int
main(int argc, char **argv)
{
  int i;
  int option;

  /* parse short options */

  while ((option = getopt(argc, argv, "bEnsTv")) != -1) {
    switch (option) {
    case 'b':
      puts("Put line numbers next to non-blank lines");
      break;
    case 'E':
      puts("Show the ends of lines as $");
      break;
    case 'n':
      puts("Put line numbers next to all lines");
      break;
    case 's':
      puts("Suppress printing repeated blank lines");
      break;
    case 'T':
      puts("Show tabs as ^I");
      break;
    case 'v':
      puts("Verbose");
      break;
    default:                          /* '?' */
      puts("What's that??");
    }
  }

  /* print the rest of the command line */

  puts("------------------------------");

  for (i = optind; i < argc; i++) {
    puts(argv[i]);
  }

  return 0;
}

如果你将此程序编译为 args,则可以尝试不同的命令行,以查看它们如何解析 短选项,并始终为你留下 命令行的其余部分。 在最简单的情况下,所有选项都在前面,你会得到这个

$ ./args -b -T file1 file2
Put line numbers next to non-blank lines
Show tabs as ^I
------------------------------
file1
file2

现在尝试相同的命令行,但将两个短选项组合成一个选项字符串

$ ./args -bT file1 file2
Put line numbers next to non-blank lines
Show tabs as ^I
------------------------------
file1
file2

如果需要,getopt 可以“重新排序”命令行以处理顺序错误的短选项

$ ./args -E file1 file2 -T
Show the ends of lines as $
Show tabs as ^I
------------------------------
file1
file2

如果你的用户给出了不正确的短选项,getopt 会打印一条消息

$ ./args -s -an file1 file2
Suppress printing repeated blank lines
./args: invalid option -- 'a'
What's that??
Put line numbers next to all lines
------------------------------
file1
file2

下载速查表

getopt 可以做的事情比我展示的要多得多。 例如,短选项可以采用自己的选项,例如 -s string-f file。 你还可以告诉 getopt 在找到无法识别的选项时不显示错误消息。 使用 man 3 getopt 阅读 getopt(3) 手册页,以了解有关 getopt 可以为你做什么的更多信息。

如果你正在寻找关于 getopt()getopt_long() 的语法和结构的温和提醒,下载我的 getopt 速查表。 一页演示了短选项,另一面演示了长选项,其中包含最少的可用代码以及你需要知道的全局变量列表。

接下来阅读什么
标签
photo of Jim Hall
Jim Hall 是一位开源软件倡导者和开发者,以在 GNOME 中进行可用性测试以及作为 FreeDOS 的创始人兼项目协调员而闻名。

评论已关闭。

Creative Commons License本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。
© . All rights reserved.