解析命令行参数(Free Pascal)
{ ======================================================================
## 功能介绍
解析命令行参数,并调用'回调函数'处理各个选项
单独的 '-' 表示下一个参数为普通参数。
单独的 '--' 表示后续参数都为普通参数。
## 举例说明
cmd - -file1 -f file2 // -file1 被识别为普通参数,-f 被识别为短选项
cmd -- -file1 -f file2 // -file1 和 -f 都被识别为普通参数
## 用法演示
```pascal
// 定义回调函数(Key 是解析出来的选项名,Value 是解析出来的选项值)
function OptHandler(const Key, Value: String; Data: Pointer): Boolean;
begin
case Key of
'a', 'aa': WriteLn(Key, ' = ', Value); // -a --aa 可以有选项值
'b', 'bb': if Value = '' then // -b --bb 可以有选项值
WriteLn('参数 ', Key, ' 必须指定选项值')
else
WriteLn(Key, ' = ', Value);
'c', 'cc': WriteLn(Key, ' 没有选项值'); // -c --cc 不能有选项值
'' : WriteLn('普通参数:', Value); // 不以 - 开头的普通参数
else WriteLn('无效选项:', Key); // 以 - 开头的其它选项
end;
// 返回 True 表示继续解析,返回 False 表示中止解析
Result := True;
end;
// 将上面创建的回调函数传递给 GetOpt 函数,开始解析
procedure Test();
begin
// 这里的 'a b aa bb' 是指 -a、-b、--aa、--bb 必须提供选项值
// 其它选项不读取选项值
if not GetOpt(Argc - 1, Argv + 1, 'a b aa bb', @OptHandler) then
WriteLn(StdErr, '命令行参数解析失败');
end;
// 可以使用下面的 Test 函数配合上面的 OptHandler 进行测试
procedure Test();
var
Args: array of String;
begin
WriteLn(#10'----- 各种参数测试 -----'#10);
Args := ['-aHello', '--aa', 'World', '-b', '', '--bb', '-', '-c', '--cc', '--', '-d', '--dd', 'hello'];
GetOpt(Length(Args), PPChar(Args), 'a b aa bb', @OptHandler);
WriteLn(#10'----- 缺少选项值测试 -----'#10);
Args := ['-a', '--aa', '-b', '--bb'];
GetOpt(Length(Args), PPChar(Args), 'a b aa bb', @OptHandler);
WriteLn(#10'----- 选项名太短测试 -----'#10);
Args := ['-aHello', '--a', 'World'];
GetOpt(Length(Args), PPChar(Args), 'a b aa bb', @OptHandler);
end;
```
====================================================================== }
type
// 用于处理单个命令行选项的回调函数
TOptHandler = function(const Key, Value: String; Data: Pointer): Boolean;
{ ----------------------------------------------------------------------
功能:循环解析命令行参数并调用回调函数处理解析结果。
参数:Argc 参数数量
Argv 参数列表,要求以 #0 结束(即要求 Argv[Argc] 为 #0)
ParamOpts 允许接受选项值的选项列表,以空格分隔,比如 `n f name file`
OptHandler 用来处理各个选项的回调函数
CustomData 传递给回调函数的自定义数据
返回:全部解析完毕则返回 True,出错或中止则返回 False。
---------------------------------------------------------------------- }
function GetOpt(Argc: Integer; Argv: PPChar; ParamOpts: String;
OptHandler: TOptHandler; CustomData: Pointer = nil): Boolean;
var
Index : Integer; // 当前正在处理的命令行参数索引
Arg : PChar; // 当前正在处理的命令行参数指针
Key : String; // 当前解析出的选项名
Value : String; // 当前解析出的选项值或普通参数
Normal : Integer; // 当前参数是否被视为普通参数,1 表示仅一次,2 表示后续全部
// 读取下一个参数
procedure NextArg();
begin
if Index <= Argc then
begin
Arg := Argv[Index];
Inc(Index);
end;
end;
// 调用回调函数,处理当前解析出来的选项名和选项值
function HandleOpt(): Boolean;
begin
Result := OptHandler(Key, Value, CustomData);
Key := '';
Value := '';
end;
begin
if Argc = 0 then
Exit(True);
Result := False;
if OptHandler = nil then
Exit;
// 前后加空格,便于查找选项名
ParamOpts := ' ' + ParamOpts + ' ';
Index := 0;
Arg := nil;
Key := '';
Value := '';
Normal := 0;
// 读取第一个选项,结果存入 Arg
NextArg();
while Index <= Argc do
begin
// 参数不以 - 开头,或参数前面指定了单独的 - 或 -- 选项,
// 处理'普通参数'或'选项值'。
if (Arg = nil) or (Arg^ <> '-') or (Normal <> 0) then
begin
Value := Arg;
if not HandleOpt() then
Exit;
NextArg();
if Normal = 1 then
Normal := 0;
end
else
// 参数以 - 开头
begin
// 单独的 - 参数
if Arg[1] = #0 then
Normal := 1
else
// - 之后跟随有内容
begin
// 上一个选项尚未处理完(因为需要选项值)
// 这里没有遇到选项值,反而遇到另一个选项名,则使用空值作为选项值
if Key <> '' then
begin
if not HandleOpt() then
Exit;
// 进入下一轮继续处理当前选项名
Continue;
end
else
// 以 -- 开头
if Arg[1] = '-' then
begin
// 单独的 -- 参数
if Arg[2] = #0 then
Normal := 2
else
// --Key 长选项
begin
// 跳过 --
Inc(Arg, 2);
// 读取选项名
Key := Arg;
// 选项名太短,传递给 OptHandler 作为非法选项处理
if Length(Key) < 2 then
Key := '--' + Key;
// 判断是否需要选项值
if Pos(' ' + Key + ' ', ParamOpts) = 0 then
begin
// 如果不需要选项值,则处理当前选项名
if not HandleOpt() then
Exit;
end;
end;
end
else
// -Key 短选项
begin
// 跳过 -
Inc(Arg);
// 循环解析连续的短选项名
while Arg^ <> #0 do
begin
// 读取一个短选项名
Key := Arg^;
Inc(Arg);
// 判断是否需要选项值
if Pos(' ' + Key + ' ', ParamOpts) = 0 then
begin
// 如果不需要选项值,则处理当前选项名
if not HandleOpt() then
Exit;
end
// 如果需要选项值,则跳出内循环,在下一轮外循环处理选项值
else
Break;
end;
// 如果当前短选项后面还紧随有字符串,则该字符串可以作为选项值使用,不需要 NextArg()
if Arg^ <> #0 then
Continue;
end;
end;
NextArg();
end;
end;
// 如果最后一个选项需要选项值,但未提供,则使用空值作为选项值
if (Key <> '') and not HandleOpt() then
Exit;
Result := True;
end;