附录 B 解析命令行选项 (Parsing Command-Line Options)

      +

      核心结论

      • getopt() 解析短选项:函数签名 int getopt(int argc, char *const argv[], const char *optstring);optstring 是「选项字符 + : 标记需参数」的序列;返回 int(不能赋给 char)。

      • 关键全局变量optarg 指向当前选项的实参;optind 指向下一个未处理 argv 元素;optopt 是当前无效选项字符;opterr 控是否打错误信息(默认 1)。

      • optstring 三种写法:默认(opterr==1)→ 错误打信息并返回 ?;前缀 : → 打不开信息,错误返 : (缺失参数) / ? (无效选项);加 + 显式关闭 GNU 的重排行为。

      • GNU 扩展:getopt() 默认会把 option 与 non-option 重排(argv 内容被改写);可设置 POSIXLY_CORRECT 或 optstring 首字符为 + 关闭。

      • GNU 长选项getopt_long() 支持 --option=value 与缩写;getopt_long_only() 允许短横线也匹配长选项。

      • 结束与未知:返回 -1 表示结束;-- 后所有字符都不视为选项。

      附录主旨

      本附录介绍 UNIX/Linux 命令行选项解析的标准库——getopt()getopt_long()。读者需理解:(1) optstring 如何编码选项与必参;(2) 全局变量的协同(optind / optarg / optopt);(3) 三种错误报告模式(默认 / opterr=0 / optstring 首 :);(4) GNU 重排 vs POSIX 严格扫描;(5) getopt_long 支持 --name[=value]。这是写几乎所有 UNIX 命令行工具必备知识。

      一、核心概念

      本附录围绕 4 个核心概念:optstring 编码、全局变量协同、错误返回模式、长选项扩展。

      概念 定义 + 重要性 实现提示

      optstring 编码

      选项字符序列:单字符表示短选项;后跟 : 表示选项需要参数;:: (GNU 扩展) 表示可选参数;首个 : 关闭错误打印;首个 + 关闭重排。

      §B;如 "p:x" 支持 -p-x":p:x" 关闭错误信息;"+p:x" 关闭重排;"::p" 可选参数。

      全局变量协同

      optind(下次扫描起点;初始 1);optarg(当前选项参数指针,nullable);opterr(错误打印开关,1=on/0=off);optopt(无效选项字符)。

      §B;多次 parse 同一 argc/argv:手动重设 optind=1(保留 extern int optreset on BSD)。

      返回与错误模式

      int:选项字符 / ? (无效选项) / : (缺参,仅当 optstring 首 :) / -1 (结束);剩余 argv[optind] 等是位置参数。

      §B;选项解析完后通常 for (i = optind; i < argc; i++) …​ 处理 positional args。

      getopt_long 与 GNU 扩展

      getopt_long 接受额外 option 数组(含长选项名、是否带参、flag 指针);GNU 风格自动重排 + 忽略首个 argv 当作程序名(与 POSIX 不同)。

      §B;getopt_long(ac, av, "+:abc:", long_opts, &longindex);flag=NULL → 返 val;flag≠NULL → 写入 *flag。

      二、详细笔记

      B.1 getopt() / optstring

      What:标准 C 库函数,扫描 argv 中以 - 开头的 token;按 optstring 解析短选项。

      Why:80% 命令行程序都需要解析 -v -h 等选项;手写 scanner 容易出错,getopt 是 POSIX 标准。

      How

      #include <unistd.h>
      extern int optind, opterr, optopt;
      extern char *optarg;
      int getopt(int argc, char *const argv[], const char *optstring);
      /* 返回:选项字符;'?' 无效;':' 缺参(optstring 首':'时);-1 结束 */

      optstring 三种构造: . "p:x"-p 后必有参数;-x 无参。 . ":p:x" :错误时不打印,缺参返 :,无效返 ?。 . "+p:x"+":p:x" :关闭 GNU 重排。

      When:(1) 任何命令行工具;(2) 写 daemon / 服务要解析参数;(3) 教学示例。

      Example:见 TLPI Listing B-1 t_getopt.c:

      // 摘自《The Linux Programming Interface》 附录 B Listing B-1
      while ((opt = getopt(argc, argv, ":p:x")) != -1) {
          switch (opt) {
          case 'p': pstr = optarg; break;
          case 'x': xfnd++;        break;
          case ':': usageError("Missing arg", optopt);
          case '?': usageError("Unrecog option", optopt);
          default : fatal("Unexpected");
          }
      }
      if (optind < argc)
          printf("First nonoption: %s at %d\n", argv[optind], optind);

      B.2 错误报告:三种模式对照

      What:getopt 错误返值与是否打印错误信息受 opterr + optstring 首位 : 控制。

      Why:写库或框架时希望不打印到 stderr;按 SUSv3 合规要求或 GNU 兼容要求选择。

      How

      配置 打错误信息? 无效选项返 缺参返

      默认(opterr=1)

      ? | ?

      opterr=0

      ? | ?

      optstring[0] == ':'

      ? | :

      optstring[0] == '+'

      重排关闭,其它按默认

      When:(1) 写 portable 程序 → +":opts" 模式;(2) 写 GNU 程序 → 默认模式;(3) 写库不要 stderr → 关错误,调用方处理。

      Example:见 TLPI Listing B-1 的 case ':'case '?' 分别处理两种错误。

      B.3 终止与剩余 argv

      What:getopt 在以下情形返回 -1: . argv[optind] 已不存在(NULL); . argv[optind][0] != '-'; . argv[optind] == "-"(单个 -,表示 stdin / 哨兵); . argv[optind] == "--"(双横线)。

      Why:parse 完后 optind < argc 仍可能有非选项参数;positional arg 用 argv[optind..argc-1] 处理。

      How:通常在 getopt 循环后:

      int opt;
      while ((opt = getopt(argc, argv, "ab:c")) != -1) { ... }
      if (optind < argc)
          /* positional args = argv[optind] ... argv[argc-1] */

      When:(1) 处理 cmd -v file1 file2;(2) 显式 -- 终止选项 grep 用法。

      Examplegrep -l pattern file1 file2patternfile1file2 是 positional args。

      B.4 getopt_long:长选项

      What:GNU 扩展 int getopt_long(int argc, char *const argv[], const char *shortopts, const struct option *longopts, int *longindex);

      struct option {
          const char *name;
          int has_arg;       /* no_argument / required_argument / optional_argument */
          int *flag;         /* NULL → 返 val;非 NULL → 返 0,val 写入 *flag */
          int val;           /* flag=NULL 时返回此值 */
      };

      Why--verbose--output=file.txt--help 让程序易用;多数现代 CLI 必备。

      How

      static struct option long_opts[] = {
          {"verbose", no_argument,       0, 'v'},
          {"output",  required_argument, 0, 'o'},
          {"help",    no_argument,       0, 'h'},
          {"name",    optional_argument, 0, 1000},   /* distinct value > short chars */
          {0,0,0,0}
      };
      int longindex;
      while ((c = getopt_long(argc, argv, ":vo:h", long_opts, &longindex)) != -1) {
          switch (c) {
          case 'v': verbose++;            break;
          case 'o': outfile = optarg;     break;
          case 1000: name = optarg ? optarg : "default"; break;
          case 'h': help(); break;
          case '?': err(...);
          case ':': err(...);
          }
      }

      When:(1) 写 user-facing 工具;(2) 想 GNU 风格 --long-name=val;(3) 解析 systemd / package manager 配置。

      Examplegetopt_long_only 让长选项允许只用单 -(不推荐)。

      B.5 GNU 重排 vs POSIX 严格扫描

      What:GNU 默认在 argv 中把 option 提前、non-option 后置(修改 argv 内存)。POSIX 默认严格按出现顺序:第一个 non-option 后停止 scan。

      Whyls file -l 在 Linux 上等价 ls -l file(GNU 行为),但脚本搬迁到 Solaris 等会失败。

      How:控制方式: . optstring 首个字符 + → 禁止重排; . 设环境变量 POSIXLY_CORRECT=1 → glibc 严格 POSIX 行为(仅 glibc 范围)。

      When:(1) 写跨 UNIX 脚本保护 → script 头 export POSIXLY_CORRECT=ychmod 644 ./ 代替 防止误解释;(2) 写 C 程序想严格 → +":opts"

      Examplechmod — 644 chmod 644 ./ 都防止文件名以 - 开头被当选项。

      三、关键图表

      optstring 字符约定
      字符 含义 备注

      c

      短选项 -c

      无参

      c:

      短选项 -c 后必带参数

      optarg 是参

      c::

      短选项 -c 后可带参数

      optarg 是 NULL 或参(GNU 扩展)

      + (首字符)

      关闭 GNU 重排

      标准

      - (首字符)

      非标准,行为未定义

      : (首字符)

      错误不打印 + 缺参返 :

      opterr=0 等价更稳定

      ? / -/其它

      被视为普通字符选项

      SUSv3 限定 a-zA-Z0-9 |

      全局变量速查
      变量 类型 含义

      optarg

      char *

      当前选项的参数(nullable when no required)

      optind

      int

      下一未处理 argv 下标(初始 1)

      opterr

      int

      错误打印开关(默认 1)

      optopt

      int

      当前不识别的选项字符(?/: 时也设置)

      optreset

      int (BSD / glibc)

      重新初始化内部状态

      四、思维导图

      mindmap
        root((附录 B getopt))
          optstring
            c 选项
            c: 必参
            c:: 可选参
            首 关闭重排
            首 关闭错误
          全局变量
            optarg 参数
            optind 进度
            optopt 错误选项
            opterr 错误开关
          返回
            选项字符
            ? 无效
            缺参
            -1 结束
            -- 终止
          错误模式
            默认打
            opterr 0
            optstring 首
          getopt_long
            struct option
            long index
            flag 指针
            GNU 扩展
          GNU 重排
            argv 改写
            optstring 首 +
            POSIXLY_CORRECT

      五、重点与易错点

      1. getopt() 返回 int,不能赋给 char 或 unsigned char*——可能在 char 无符号的平台上与 -1 比较失败。

      2. 重新解析同一 argv 必须 optind=1——glibc 保留内部状态,可再用 optreset=1 清空(BSD 风格)。

      3. argv 在 GNU 模式下会被修改——若 argv 不在你控制(如 const char *const *argv),可能 crash;SUSv3 不允许此行为但 glibc 这么做。

      4. optstring 含 : 时区分缺参与无效选项:写自定义错误处理时 case ':'case '?' 都要写。

      5. optstring 第一个字符 + 关闭重排——需要 POSIX 严格行为时一律 +:。

      6. GNU getopt_long 接受 - 单字符 + 单短选项——但 -x 总是先尝试 short,没匹配才试 long。

      7. optional_argument 的 GNU 扩展——必须写成 c::,与 trailing arg 同 word 才识别(--name=value);空格后被当作 positional。

      8. getopt_long 中 flag=NULL 才会返 val——若 flag 非 NULL,则返 0,val 写入 *flag

      9. argv[0] 是程序名——getopt_long(argc, argv, …​) 第一参数 argc 不应该 -1;SUSv3 让 getopt 从 argv[1] 起;glibc 也允许从 argv[0] 起始(GNU 风格)。

      10. 命令行以 -- 终止选项——grep -l — -v file---v 被当搜索内容。

      11. 写 portable 工具:optstring = +":" + shortopts;不要用 GNU c::;不要依赖 argv 修改。

      12. 不要用 char c = getopt();——必须 int c = getopt();;否则与 -1 比较不对。

      13. 写 C++ 库 + getopt——非 main argv 不要破坏;考虑 Boost.Program_options / getopt_long 自封装。

      14. Linux 默认 LANG=C 行为——切换 LANG 影响某些 getopt 错误信息;测试时 fixed C locale。

      15. 跨章衔接:本章为第 60 章 server(-d 选项)解析铺垫;第 9 章 set-user-ID 程序也用 getopt(注意 secure_getopt 一般不可用);第 50 章 mlockall 等 program options 命令行工具。