使用 PHP 的 getopt() 后,如何确定还剩下哪些参数?

27

好的,所以PHP有一个内置的getopt()函数,它可以返回用户提供的程序选项的信息。只是,除非我错过了什么,否则它完全失效了!从手册中可以看到:

选项的解析将在第一个找到的非选项处结束,其后跟随的任何内容都将被丢弃。

所以getopt()函数返回一个包含有效和已解析选项的数组。您仍然可以通过查看未修改的$argv来查看整个原始命令行,但是如果您想将命令行的剩余部分视为其他内容(例如文件名),您该如何告诉getopt()停止解析参数的位置?这是必须知道的。

以下是一个例子...

假设我想设置一个脚本来接受以下参数:

Usage: test [OPTION]... [FILE]...

Options:
  -a  something
  -b  something
  -c  something

那么我可能会像这样调用getopt()

$args = getopt( 'abc' );

如果我按照以下方式运行脚本:

$ ./test.php -a -bccc file1 file2 file3

我预计会收到以下数组返回:
Array
(
    [a] =>
    [b] =>
    [c] => Array
        (
            [0] =>
            [1] =>
            [2] =>
        )
)

所以问题在于:我怎么可能知道这三个未解析的、非选项的FILE参数是从$argv[3]开始的呢?

1
你可以查看 Console_GetOpt(https://dev59.com/F0jSa4cB1Zd3GeqPChgk),但它需要 PEAR。 - andig
1
你将文件指定为最后一个参数,因此可以执行$argv[count($argv) - 1] - EaterOfCode
2
@EaterOfCode:那不够。你可以指定任意数量的文件,因此你需要知道在末尾有多少未解析的非选项参数。 - edam
2
如果你没有需要值或可选值的参数,你可以简单地去除所有以连字符开头的 $argv 元素,只留下非选项参数:preg_grep('/^-/', $argv, PREG_GREP_INVERT) - Quinn Comendant
哈哈,我花了30分钟才解决这个问题,结果发现了我自己写的旧评论。 ;) 解决方案仍然有效。更新用法:$argv = array_values(preg_grep('/^-/', $argv, PREG_GREP_INVERT)); - Quinn Comendant
使用PHP 7.1及更高版本,有一个可选的第三个参数$&optind,它接收剩余参数的第一个参数的索引。我不知道如何在PHP 7.0和早期版本中解决这个问题。所有这些版本现在都已经到达了生命周期的尽头,所以如果可以的话,您应该升级。 - Enno
4个回答

7

从PHP7.1开始,getopt支持一个可选的按引用传递的参数&$optind,其中包含参数解析停止的索引。这对于混合使用标志和位置参数非常有用。例如:

user@host:~$ php -r '$i = 0; getopt("a:b:", [], $i); print_r(array_slice($argv, $i));' -- -a 1 -b 2 hello1 hello2
Array
(
    [0] => hello1
    [1] => hello2
)

我在2021年写作,仍然受限于PHP 5.5,无法使用getopt()的第三个选项。 - ob-ivan

2
以下内容可用于获取命令行选项后的所有参数。在调用PHP的 getopt()之前或之后使用都不会改变结果:
# $options = getopt('cdeh');

$argx = 0;

while (++$argx < $argc && preg_match('/^-/', $argv[$argx])); # (no loop body)

$arguments = array_slice($argv, $argx);

$arguments 包含了除了前置选项之外的任何参数。如果您不想将参数作为单独的数组,则 $argx 是第一个实际参数的索引:$argv[$argx]

如果没有参数在前置选项之后,则:

  • $arguments 是一个空数组 [],且
  • count($arguments) == 0,以及
  • $argx == $argc

1
这是一个糟糕的答案!它跳过任何以“-”开头的命令行参数,并假设其余的都是非选项参数!如果我运行这样的命令行:./example --name Bob -s 23,那怎么办?在这里,我可能没有非选项参数(如果--name和-s都需要一个选项参数),而您的代码会说["Bob", "-s", "23"]是非选项参数。问题在于,在解析选项之前,无法确定哪些参数是非选项参数。PHP的getopt()需要以某种方式指示哪些参数不是选项。但它没有。 - edam

2

没有人说你必须使用 getopt。你可以按照自己的方式进行操作:

$arg_a = null; // -a=YOUR_OPTION_A_VALUE
$arg_b = null; // -b=YOUR_OPTION_A_VALUE
$arg_c = null; // -c=YOUR_OPTION_A_VALUE

$arg_file = null;  // -file=YOUR_OPTION_FILE_VALUE

foreach ( $argv as $arg )
{
    unset( $matches );

    if ( preg_match( '/^-a=(.*)$/', $arg, $matches ) )
    {
        $arg_a = $matches[1];
    }
    else if ( preg_match( '/^-b=(.*)$/', $arg, $matches ) )
    {
        $arg_b = $matches[1];
    }
    else if ( preg_match( '/^-c=(.*)$/', $arg, $matches ) )
    {
        $arg_c = $matches[1];
    }
    else if ( preg_match( '/^-file=(.*)$/', $arg, $matches ) )
    {
        $arg_file = $matches[1];
    }
    else
    {
        // all the unrecognized stuff
    }
}//foreach

if ( $arg_a === null )    { /* missing a - do sth here */ }
if ( $arg_b === null )    { /* missing b - do sth here */ }
if ( $arg_c === null )    { /* missing c - do sth here */ }
if ( $arg_file === null ) { /* missing file - do sth here */ }

echo "a=[$arg_a]\n";
echo "b=[$arg_b]\n";
echo "c=[$arg_c]\n";
echo "file=[$arg_file]\n";

我总是这样做,这样可以正常工作。而且我可以随心所欲地处理它。


2
是的,但我更喜欢能够使用内置库,而不是随时随地携带自己的库。对我来说,这似乎是内置库的一个严重缺陷。(另外,你的示例代码无法处理例如我的示例中的“-bccc”命令行参数。我相信有更完整的PHP命令行解析器示例,但那样做有点违背了这个问题的意义!) - edam
只需使用 strtok 并使用 foreach 循环,结果会更好。 - Gasol
1
我遇到过几种情况,PHP内置函数的效果不太理想,我需要自己构建函数。我认为这不应该被贬低,因为这是在有创意地尝试解决PHP内置函数无法解决的问题。虽然这不是完美的答案,但也不是一个糟糕的答案。 - n0nag0n
@ImmortalFirefly:虽然它没有回答问题——我已经知道我可以自己编写!但我的问题是关于如何使用PHP内置的getopt()来实现它预期的目的。 - edam

0
看一下 GetOptionKit,摆脱标志解析。

http://github.com/c9s/GetOptionKit

GetOptionKit 可以很容易地集成到你的命令行脚本中。它支持类型约束、值验证等功能。


谢谢。我知道还有其他库可以做到这一点。我只是想知道是否有一种方法可以使用PHP的本地getopt()函数来实现。不过似乎并没有。 - edam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接