27

OK, So PHP has a built-in getopt() function which returns information about what program options the user has provided. Only, unless I'm missing something, it's completely borked! From the manual:

The parsing of options will end at the first non-option found, anything that follows is discarded.

So getopt() returns an array with the valid and parsed options only. You can still see the entire original command-line by looking at $argv, which remains unmodified, but how do you tell where in that command-line getopt() stopped parsing arguments? It is essential to know this if you want treat the remainder of a command-line as other stuff (like, e.g., file names).

Here's an example...

Suppose I want to set up a script to accept the following arguments:

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

Options:
  -a  something
  -b  something
  -c  something

Then I might call getopt() like this:

$args = getopt( 'abc' );

And, if I ran the script like this:

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

I should expect to have the following array returned to me:

Array
(
    [a] =>
    [b] =>
    [c] => Array
        (
            [0] =>
            [1] =>
            [2] =>
        )
)

So the question is this: How on Earth am I supposed to know that the three unparsed, non-option FILE arguments start at $argv[ 3 ]???

edam
  • 718
  • 2
  • 8
  • 13
  • 1
    You could look into Console_GetOpt (http://stackoverflow.com/questions/1023095/phps-command-line-option-parsing-howto) but it requires PEAR. – andig Dec 10 '13 at 06:35
  • 1
    You specified the file as last parameter so you could do `$argv[count($argv) - 1]` – EaterOfCode Jan 26 '15 at 12:07
  • 2
    @EaterOfCode: that's not sufficient. You can specify any number of files, so you need to know how many unparsed non-option arguments there are at the end. – edam Feb 26 '15 at 12:04
  • 2
    If you have no parameters requiring values or optional values, you can simply trim all `$argv` elements starting with a hyphen, leaving only non-option arguments: `preg_grep('/^-/', $argv, PREG_GREP_INVERT)` – Quinn Comendant Jun 21 '15 at 00:59
  • Haha, after for 30 minutes to solve this problem I found my own, old comment above. ;) The solution is still value. Updated usage: `$argv = array_values(preg_grep('/^-/', $argv, PREG_GREP_INVERT));` – Quinn Comendant Aug 24 '17 at 09:21
  • With PHP 7.1 and later, there's an optional third parameter &$optind that receives the index of the first argument that remains. I don't know how to solve this for PHP 7.0 and earlier versions. All of those have now reached end of life, so if you can, you should upgrade. – Enno May 01 '19 at 14:36

4 Answers4

6

Starting with PHP 7.1, getopt supports an optional by-ref param, &$optind, that contains the index where argument parsing stopped. This is useful for mixing flags with positional arguments. E.g.:

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
)
adsr
  • 160
  • 1
  • 7
  • I'm writing in 2021 that I'm still constrained to PHP 5.5 and I cannot use the third option to `getopt()`. – ob-ivan Jan 22 '21 at 17:03
2

Nobody said you have ot use getopt. You may do it in any way you like:

$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";

I always do it like that and it works. Moreover I can do whatever I want with it.

Artur
  • 7,038
  • 2
  • 25
  • 39
  • 2
    Yeah, but I'd prefer to be able to use the built-in library, rather than hoick my own one around with me everywhere. This seems like a serious flaw in the built-in library, to me.(Also, your example code won't handle, e.g., the `-bccc` command-line parameter in my example. I'm sure there are more complete examples of command-line parsers for PHP out there, but that sort of defeats the point of this question!) – edam Oct 03 '13 at 14:55
  • Simply use `strtok` and foreach the results will be better. – Gasol Dec 19 '13 at 04:11
  • 1
    I've run into several situations where PHP's built in functions miss the mark a little and I've needed to build my own. I don't think this should be downvoted because it's creatively trying to solve the problem that PHP's built in function doesn't answer. It's not a perfect answer, but it's not a crappy one. – n0nag0n Sep 04 '15 at 18:50
  • @ImmortalFirefly: it doesn't address the question, though--I already know I could roll my own! My question was specifically about how I can use PHP's in-built getopt() for the purpose that it was intended. – edam Feb 01 '16 at 11:20
2

The following may be used to obtain any arguments following command line options. It can be used before or after invoking PHP's getopt() with no change in outcome:

# $options = getopt('cdeh');

$argx = 0;

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

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

$arguments now contains any arguments following any leading options. Alternatively, if you don't want the argument(s) in a separate array, then $argx is the index of the first actual argument: $argv[$argx].

If there are no arguments, after any leading options, then:

  • $arguments is an empty array [], and
  • count($arguments) == 0, and
  • $argx == $argc.
  • 1
    This is a terrible answer! It skips over any command line arguments that begin with a `-` and assumes that the rest are non-option arguments! What if I run a command line this: `./example --name Bob -s 23`? Here I may have *no* non-option arguments (if --name and -s both take an option argument), and your code would say that `["Bob", "-s", "23"]` were non-option arguments. The problem is that *until* you parse the options, you *can not tell* which arguments are non-option arguments. PHP's `getopt()` would need to indicate which arguments are not part of an option, somehow. And it doesn't. – edam May 20 '16 at 12:35
0

Take a look at GetOptionKit to get rid of the flag parsing.

http://github.com/c9s/GetOptionKit

GetOptionKit can be easily integrated into your command line script. It supports type constraint, value validation and so on.

c9s
  • 1,888
  • 19
  • 15
  • Thanks. I'm aware that there are other libraries to do this. I just wondered if there is a way to do it with PHP's native `getopt()` function. Seems there isn't though. – edam May 20 '16 at 12:28