C 語言:getopt_long —— POSIX 標準、零依賴、高效能
核心 API
#include <getopt.h>
struct option {
const char *name; // 長選項名(無 --)
int has_arg; // no_argument / required_argument / optional_argument
int *flag; // NULL 時返回 val;非 NULL 時 *flag = val,返回 0
int val; // 短選項字元或自定義值
};
int getopt_long(int argc, char *const argv[],
const char *optstring, // 短選項:「i:p:s:o:h」冒號=需參數
const struct option *longopts,
int *longindex); // 可選,返回匹配的 longopts 索引
完整範例:UDP 發送工具參數解析
/* udp_send.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
static const struct option long_opts[] = {
{"ip", required_argument, NULL, 'i'},
{"port", required_argument, NULL, 'p'},
{"host", required_argument, NULL, 's'},
{"out", required_argument, NULL, 'o'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
static void usage(const char *prog) {
fprintf(stderr,
"Usage: %s [OPTIONS]\n"
" -i, --ip=ADDR UDP 目標 IP (預設 225.1.1.31)\n"
" -p, --port=PORT UDP 目標埠 (預設 1234)\n"
" -s, --host=ADDR 本地綁定 IP (預設 127.0.0.1)\n"
" -o, --out=FILE 輸出檔案 (預設 udp.dat)\n"
" -h, --help 顯示說明\n", prog);
}
int main(int argc, char *argv[]) {
char ip[32] = "225.1.1.31";
int port = 1234;
char host[32] = "127.0.0.1";
char outfile[256] = "udp.dat";
int c;
opterr = 0; // 關閉預設錯誤訊息,自行處理
while ((c = getopt_long(argc, argv, "i:p:s:o:h", long_opts, NULL)) != -1) {
switch (c) {
case 'i': strncpy(ip, optarg, sizeof(ip)-1); break;
case 'p': port = atoi(optarg); break;
case 's': strncpy(host, optarg, sizeof(host)-1); break;
case 'o': strncpy(outfile, optarg, sizeof(outfile)-1); break;
case 'h': default: usage(argv[0]); return c == 'h' ? 0 : 1;
}
}
printf("ip : %s\nport : %d\nhost : %s\nout : %s\n", ip, port, host, outfile);
/* 處理非選項參數 */
for (int i = optind; i < argc; ++i)
printf("Non-option arg: %s\n", argv[i]);
return 0;
}
編譯測試
$ gcc -std=c11 -Wall udp_send.c -o udp_send
$ ./udp_send --ip=192.168.1.100 -p 5000 -o data.bin extra1 extra2
ip : 192.168.1.100
port : 5000
host : 127.0.0.1
out : data.bin
Non-option arg: extra1
Non-option arg: extra2
進階技巧
- 選項分組:
optstring = "abc:d::" → -abc file 等同 -a -b -c file
- 可選參數:
:: 表示 -o[file],optarg 可能為 NULL
- 長選項別名:在
struct option 中重複 val 不同、name 不同
- 子命令模式:先解析全域選項,再
argv[optind] 為子命令,遞迴調用解析器
Perl:Getopt::Long —— 表達力強、類型豐富、一行搞定
核心語法
use Getopt::Long qw(GetOptions);
my ($params, $key, $verbose, $help);
GetOptions(
"params|p=s" => \$params, # 字串,必填
"key|k=i" => \$key, # 整數
"verbose" => \$verbose, # 布林旗標
"help|h" => \$help, # 別名
) or die "Invalid options\n";
die "Usage: $0 --params=... --key=123 [--verbose]\n" if $help || @ARGV == 0;
類型修飾符完整表
| 修飾符 | 含義 | 範例 |
=s | 字串(可為 - 開頭) | --file=- |
=i | 整數(支援 0x, 0b, 0 前綴) | --num=0xff |
=o | 擴充整數(八進位/十六進位/二進位) | --mode=0755 |
=f | 浮點數 | --rate=3.14 |
! | 布林可取反 | --debug / --nodebug |
+ | 計數器 | -v -v -v → 3 |
:@ | 陣列值(多次出現) | --inc=foo --inc=bar |
:% | 雜湊值 | --define=k=v |
進階功能展示
# 複雜驗證與預設值
my $config = {
threads => 4,
level => 'info',
};
GetOptions(
"threads=i" => \$config->{threads},
"level=s" => sub { # 自定義驗證
my ($opt, $val) = @_;
die "Invalid level" unless $val =~ /^(debug|info|warn|error)$/;
$config->{level} = $val;
},
"dry-run!" => \$config->{dry_run},
) or exit 1;
兩語言選型指南
| 維度 | C (getopt_long) | Perl (Getopt::Long) |
| 部署依賴 | 零(libc 內建) | 需 Perl 執行環境 |
| 效能 | 極高(微秒級) | 啟動開銷較大 |
| 類型系統 | 手動轉換 atoi/strtod | 內建 =i/f/o 自動驗證 |
| 錯誤訊息 | 自行格式化 | 自動生成詳細用法 |
| 維護性 | 結構體表單集中管理 | 宣告式、可讀性極佳 |
| 適用場景 | 系統工具、嵌入式、高效能 CLI | 自動化腳本、原型、複雜驗證邏輯 |
最佳實踐清單(通用)
- 短選項單字母,長選項完整單詞(
-h/--help)
- 預設值在程式碼中集中定義,避免散落
- 未知選項/缺參數統一輸出
stderr 並返回非零碼
-- 結束選項解析,後續視為位置參數(雙方均支援)
- 單元測試覆蓋:正常、缺參、錯誤、邊界、
--help、位置參數混合