附录 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 命令行选项解析的标准库—— |
一、核心概念
本附录围绕 4 个核心概念:optstring 编码、全局变量协同、错误返回模式、长选项扩展。
| 概念 | 定义 + 重要性 | 实现提示 |
|---|---|---|
optstring 编码 |
选项字符序列:单字符表示短选项;后跟 |
§B;如 |
全局变量协同 |
|
§B;多次 parse 同一 argc/argv:手动重设 optind=1(保留 |
返回与错误模式 |
返 |
§B;选项解析完后通常 |
getopt_long 与 GNU 扩展 |
|
§B; |
二、详细笔记
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) |
是 |
|
|
否 |
|
|
否 |
|
|
重排关闭,其它按默认 |
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 用法。
Example:grep -l pattern file1 file2 中 pattern、file1、file2 是 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 配置。
Example:getopt_long_only 让长选项允许只用单 -(不推荐)。
B.5 GNU 重排 vs POSIX 严格扫描
What:GNU 默认在 argv 中把 option 提前、non-option 后置(修改 argv 内存)。POSIX 默认严格按出现顺序:第一个 non-option 后停止 scan。
Why:ls file -l 在 Linux 上等价 ls -l file(GNU 行为),但脚本搬迁到 Solaris 等会失败。
How:控制方式:
. optstring 首个字符 + → 禁止重排;
. 设环境变量 POSIXLY_CORRECT=1 → glibc 严格 POSIX 行为(仅 glibc 范围)。
When:(1) 写跨 UNIX 脚本保护 → script 头 export POSIXLY_CORRECT=y 或 chmod 644 ./ 代替 防止误解释;(2) 写 C 程序想严格 → +":opts"。
Example:chmod — 644 与 chmod 644 ./ 都防止文件名以 - 开头被当选项。
三、关键图表
|
optstring 字符约定
|
|
全局变量速查
|
四、思维导图
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
五、重点与易错点
-
getopt() 返回 int,不能赋给 char 或 unsigned char*——可能在 char 无符号的平台上与 -1 比较失败。
-
重新解析同一 argv 必须 optind=1——glibc 保留内部状态,可再用 optreset=1 清空(BSD 风格)。
-
argv 在 GNU 模式下会被修改——若 argv 不在你控制(如 const char *const *argv),可能 crash;SUSv3 不允许此行为但 glibc 这么做。
-
optstring 含
:时区分缺参与无效选项:写自定义错误处理时case ':'与case '?'都要写。 -
optstring 第一个字符
+关闭重排——需要 POSIX 严格行为时一律+:。 -
GNU
getopt_long接受-单字符 + 单短选项——但-x总是先尝试 short,没匹配才试 long。 -
optional_argument 的 GNU 扩展——必须写成
c::,与 trailing arg 同 word 才识别(--name=value);空格后被当作 positional。 -
getopt_long 中
flag=NULL才会返val——若 flag 非 NULL,则返 0,val 写入*flag。 -
argv[0] 是程序名——getopt_long(argc, argv, …) 第一参数 argc 不应该 -1;SUSv3 让 getopt 从 argv[1] 起;glibc 也允许从 argv[0] 起始(GNU 风格)。
-
命令行以
--终止选项——grep -l — -v file中--后-v被当搜索内容。 -
写 portable 工具:optstring =
+":" + shortopts;不要用 GNUc::;不要依赖 argv 修改。 -
不要用
char c = getopt();——必须int c = getopt();;否则与 -1 比较不对。 -
写 C++ 库 + getopt——非 main argv 不要破坏;考虑 Boost.Program_options / getopt_long 自封装。
-
Linux 默认
LANG=C行为——切换 LANG 影响某些 getopt 错误信息;测试时 fixed C locale。 -
跨章衔接:本章为第 60 章 server(-d 选项)解析铺垫;第 9 章 set-user-ID 程序也用 getopt(注意 secure_getopt 一般不可用);第 50 章 mlockall 等 program options 命令行工具。