Using Console_Getopt to Process the PHP Command Line

December 23, 2004

Tutorials

Your Wish Is My Command Line
The Long And Short Of It
Pepperoni or Cheese?
Breaking the Code

A Full Meal
A Question of Value
The Long Version
Turning the Tables
Summary
About the Author


Your Wish Is My Command Line

The traditional view of PHP is that of a server-side scripting
language, embedded in HTML documents and invoked by a Web server. In
fact, if you’re new to PHP, you might be forgiven for thinking this is
the only view – after all, it’s quite likely that you haven’t
yet seen PHP being used in any other context.

Well, surprise, surprise – the PHP distribution includes a
command-line interface, which allows you to create and run PHP scripts
at the command line without needing either a Web server or an HTML
wrapper. This command-line interface, or CLI, is mostly used for shell
scripting, and comes in very handy for automating routine
console-based tasks… especially if you’re already familiar with PHP
and don’t really want to learn another language such as Perl or bash.

Now, you probably already know how to send options to a PHP program
to modify its behaviour. With a web-based PHP application, these
options may be passed through a form, through session variables, or in
the URL string. With a console-based PHP script that runs through the
CLI, the options must be passed on the command-line itself. Of course,
this means that you need a way to parse these options, decide what
each one means, and modify the behaviour of the script appropriately.

That’s where this tutorial comes in – it shows you how to painlessly
support command-line options in your PHP CLI programs. It’s not as
difficult as it might seem at first glance – and no, you don’t need to
stay up all night to get it done. All you really need is a copy of
Console_Getopt.


The Long And Short Of It

The Console_Getopt class is a PEAR package that provides an API for
PHP developers to capture options passed to a PHP program on the command
line, and act on them within the business logic of the program. It’s currently
maintained by Andrei
Zmievski
and Stig Bakken,
- both of whom are PHP core developers – and is freely available from

http://pear.php.net/package/Console_Getopt, or as part of the
standard PHP distribution. It goes without saying that you will also need
to have PEAR itself (PEAR.php) installed in order to use this package.

Command-line options usually come in two sizes: short and long. In the
short format, these options are usually preceded by a dash, as in the
following example:

shell> php -h

Under the POSIX standard, it’s also possible to use longer, human-readable
command-line options, preceded with a double dash, as in the following example:


shell> php --help

You can also pass values to a program from the command-line interface, like this:

shell> lynx --term=VT100

The Console_Getopt class works with both short and long options, and
also supports option values. The class conforms to PEAR rules on error
handling and coding syntax and is, in fact, a fairly important component
of PEAR: the PEAR package manager itself uses Console_Getopt for many tasks.


Pepperoni Or Cheese?

To see how Console_Getopt works, copy and paste the following PHP script and
save it as pizza.php:


<?php

// include class file

include ("Console/Getopt.php");

// initialize object

$cg = new Console_Getopt();

/* define list of allowed options - p = pepperoni  t = tomato  o = olives  a = anchovies  h = ham */

$allowedShortOptions = "ptoah";

// read the command line

$args = $cg->readPHPArgv();

// get the options

$ret = $cg->getopt($args, $allowedShortOptions);

// check for errors and die with an error message if there was a problem

if (PEAR::isError($ret)) {

    die ("Error in command line: " . $ret->getMessage() . "\n");

}

// display the options

print_r($ret);

?>

How does this work? It’s quite simple: the first step is to create a
new Console_Getopt() object, and define a list of valid
command-line arguments for the program.


<?php

include ("Console/Getopt.php");

$cg = new Console_Getopt();

$allowedShortOptions = "ptoah";

?>

In this example, the program can be invoked with any or all of the
options -p, -a, -t, -o and
-h. I’m assuming only short options here, but you could define
long options too – I’ll get to that shortly. Note that the order of options in
the $allowedShortOptions variable is not important.

Once the option list is defined, the Console_Getopt() object’s
readPHPArgv() method retrieves the command line which was
used to invoke the program, by looking in the special $_SERVER['argv']
or (for back compatibility) $HTTP_SERVER_VARS['argv'] arrays.


<?php

$args = $cg->readPHPArgv();

?>

Next, the object’s getopt() method parses the command line and
matches it against the list of valid options defined earlier (this list will be
passed to getopt() as the second argument).


<?php

$ret = $cg->getopt($args, $allowedShortOptions);

?>


In the event of an error – for example, if an invalid option is specified on
the command line – a PEAR::Error() object is returned, and can
be captured by the script:


<?php

if (PEAR::isError($ret)) {

    die ("Error in command line: " . $ret->getMessage() . "\n");

}

?>

If no error occurs, getopt() returns an array containing a list of
the options passed, as well as a list of non-option arguments. To see what this
array contains, scroll on down.


Breaking The Code

Although it’s been around since PHP 4.2.0, PHP CLI has suffered
many name changes and relocations – look at
http://www.php.net/manual/en/features.commandline.php for more
information, and to check which of its many avatars you have. Once
you’ve figured out where it is and what it’s called, invoke it and run
the script above with some command-line arguments, as below:

shell> php pizza.php -p -t

You should see something like this:

Array
(
    [0] => Array
        (
            [0] => Array
                (
                    [0] => p
                    [1] =>
                )

            [1] => Array
                (
                    [0] => t
                    [1] =>
                )

        )

    [1] => Array
        (
        )

)

Yes, I know – it looks exactly like that nightmare you had last
night. But hang on, it’s not really that hard to decipher.

The array returned by the getopt() method always contains two
elements, which are themselves arrays: the first holds the arguments passed
to the program (identified by a preceding single or double dash), while the
second holds all the non-option arguments.

Each argument passed to the program is itself represented as a two-element
array inside the first outer array. The first of these elements holds the
argument itself – for example, p or a – while the
second holds the value corresponding to it. I haven’t yet introduced argument
values, which is why the second element is empty – you’ll see it fill up in
later examples. If you look at the command line used in the example above, you
can clearly see the correspondence between the arguments and the structure of
the first array.

The second array holds the list of non-option arguments passed to the program:
one element for each argument. The greater the number of arguments, the larger
the second array.

To better understand how this works, try running the script above with different
arguments, and inspecting the returned array. For example, running it with the command line

shell> php pizza.php -pta crispy large

returns:

Array
(
    [0] => Array
        (
            [0] => Array
                (
                    [0] => p
                    [1] => 
                )

            [1] => Array
                (
                    [0] => t
                    [1] => 
                )

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

        )

    [1] => Array
        (
            [1] => crispy
            [2] => large
        )

)

Notice that you can combine short options, and that the extra non-option arguments
crispy and large have made it into the second array.
Notice also that if you attempt to use an option which is not specified in the
$allowedOptions list, the script generates an error:

shell> php pizza.php -x
Error in command line: unrecognized option -- x


A Full Meal

Now that you know what the return value of getopt() looks like,
let’s revise the script above to actually parse the options passed to it and
do something with them. Here goes:


<?php

// include class file

include ("Console/Getopt.php");

// initialize object

$cg = new Console_Getopt();

/* define list of allowed options:  p = pepperoni  t = tomato  o = olives  a = anchovies  h = ham */

$allowedShortOptions = "ptoah";

// read the command line

$args = $cg->readPHPArgv();

// get the options

$ret = $cg->getopt($args, $allowedShortOptions);

// check for errors and die with an error message if there was a problem

if (PEAR::isError($ret)) {

    die (
"Error in command line: " . $ret->getMessage() . "\n");

}

// now parse the options array

$opts = $ret[0];

if (
sizeof($opts) > 0) {

    // if at least one option is present

    
echo "You selected a pizza with:";

    foreach (
$opts as $o) {

        switch (
$o[0]) {

            case
'p':

                echo " pepperoni";

                break;

            case
'o':

                echo
" olives";

                break;

            case
'h':

                echo
" ham";

                break;

            case 'a':

                echo
" anchovies";

                break;

            case
't':

                echo
" tomatoes";

                break;

        }

    }

    echo "\n";

}

else {

    
// if no options are present

    
echo "Your pizza has no toppings.\n";

}

// now deal with the non-option arguments

$args = $ret[1];

if (sizeof($args) > 0) {

    
// if arguments were present

    
echo "You also said: ";

    foreach (
$args as $a) {

        echo "$a ";

    }

echo
"\n";

}

?>

The addition made to the script take care of (1) isolating the first
and second elements of the array returned by getopt()

into separate arrays called $opts and $args;
(2) iterating over $opts and translating each short option
into a human-readable string; and (3) iterating over $args
and printing each non-option argument.

Here are some usage examples:


shell> php pizza.php -pta
You selected a pizza with: pepperoni tomatoes anchovies
shell> php pizza.php -h -o -t
You selected a pizza with: ham olives tomatoes

shell> php pizza.php -h -o -t crisp extralarge fast
You selected a pizza with: ham olives tomatoes
You also said: crisp extralarge fast
shell> php pizza.php
Your pizza has no toppings.
shell> php pizza.php nocheese
Your pizza has no toppings.
You also said: nocheese


A Question of Value

Console_Getopt also allows you to read in string or numeric values,
and define whether those values are mandatory or (excuse the pun)
optional. To tell Console_Getopt that a particular option must always
be accompanied with a value, place a colon (:) after the
option in the list of allowed options. To make the value optional, use
a double colon (::). So, for example, the option string


<?php

$options = "p:q:r::";

?>


would imply that you must supply values when using options -p
and -q, but you can use option -r with or without
a value.

Here’s an example that illustrates how this works:


<?php

// include class file

include ("Console/Getopt.php");

// initialize object

$cg = new Console_Getopt();

// define list of allowed options:  -s = size  -c = cheese

$allowedShortOptions = "s:c::";

// read the command line

$args = $cg->readPHPArgv();

// get the options

$ret = $cg->getopt($args, $allowedShortOptions);

// check for errors and die with an error message if there was a problem

if (PEAR::isError($ret)) {

    die (
"Error in command line: " . $ret->getMessage() . "\n");

}

// now parse the options array

$opts = $ret[0];

if (
sizeof($opts) > 0) {

    // if at least one option is present

    
foreach ($opts as $o) {

        switch (
$o[0]) {

            
// handle the size option

            
case 's':

                
$size = $o[1];

                echo "Pizza size: $size inches\n";

                break;

            
/* handle the cheese option. Since a value is optional,

            check if a value is present and use the default if not */

            
case 'c':

                
$cheese = $o[1];

                echo !empty(
$cheese) ? "Pizza cheese: $cheese\n" : "Pizza cheese: mozzarella\n";

                break;

        }

    }

}

?>

Most of this is similar to what you saw in the earlier example. However, in
this version, the program requires you to optionally choose a pizza size
(-s) and a cheese type (-c). If you decide to
use the -s option, you must specify the size you require. If
you decide to use the -c option, you can either have the chef
use the default cheese, or you can state which cheese you’d prefer.

Here are a few examples in action:

shell> php pizza.php -s12
Pizza size: 12 inches
shell> php pizza.php -s12 -c

Pizza size: 12 inches
Pizza cheese: mozzarella
shell> php pizza.php -s12 -cgruyere
Pizza size: 12 inches
Pizza cheese: gruyere

shell> php pizza.php -s12 -c"quatro formaggio"
Pizza size: 12 inches
Pizza cheese: quatro formaggio

Look what happens if you try using an option that requires a value,
without a value – Console_Getopt slaps your wrist immediately!

shell> php pizza.php -s
Error in command line: Console_Getopt: option requires an argument -- s


The Long Version

As noted earlier, Console_Getopt also supports the longer, human-readable form
of command-line options. In order to use this feature, create an array containing
the valid options and provide it to the getopt() method as a third
argument. The next example illustrates, by including longer equivalents for the
short options in the previous example:


<?php

// include class file

include ("Console/Getopt.php");

// initialize object

$cg = new Console_Getopt();

// define list of allowed short options:  -s = size  -c = cheese

$allowedShortOptions = "s:c::";

// define list of allowed long options

$allowedLongOptions = array("size=", "cheese==");

// read the command line

$args = $cg->readPHPArgv();

// get the options

$ret = $cg->getopt($args, $allowedShortOptions, $allowedLongOptions);

// check for errors and die with an error message if there was a problem

if (PEAR::isError($ret)) {

    die ("Error in command line: " . $ret->getMessage() . "\n");

}

// now parse the options array

$opts = $ret[0];

if (sizeof($opts) > 0) {

    
// if at least one option is present

    
foreach ($opts as $o) {

        switch (
$o[0]) {

            // handle the size option

            
case 's':

            case
'--size':

                
$size = $o[1];

                echo
"Pizza size: $size inches\n";

                break;

            /* handle the cheese option.  Since a value is optional,

            check if a value is present and use the default if not */

            
case 'c':

            case
'--cheese':

                
$cheese = $o[1];

                echo !empty(
$cheese) ? "Pizza cheese: $cheese\n" : "Pizza cheese: mozzarella\n";

                break;

        }

    }

}

?>

The call to getopt() here includes a new argument: the
$allowedLongOptions array, which contains a list of valid long
options. These long options are processed in exactly the same manner as the
short options discussed previously. Notice that the equality symbol =

replaces the colon : when setting up the rules for option values.

Here are some examples of using the longer option format:

shell> php pizza.php --size=12
Pizza size: 12 inches
shell> php pizza.php --size=12 --cheese=none
Pizza size: 12 inches
Pizza cheese: none

Long and short options can coexist very nicely with each other on
the same command line – as illustrated below:

shell> php pizza.php -s12 --cheese

Pizza size: 12 inches
Pizza cheese: mozzarella


Turning The Tables

Now that you know how Console_Getopt works, let’s look at something
approaching a real-world application. This next script connects to a MySQL
server and returns a list of all the databases and tables found. The MySQL
host, username and password can be specified as command-line options, and an
additional database name can also be specified to restrict the search
to a single database. Here’s the code:


<?php

// include class file

include ("Console/Getopt.php");

// initialize object

$cg = new Console_Getopt();

// define list of allowed short options

$allowedShortOptions = "h:u:p:d::";

// define list of allowed long options

$allowedLongOptions = array("host=", "user=", "pass=", "db==");

// read the command line

$args = $cg->readPHPArgv();

// get the options

$ret = $cg->getopt($args, $allowedShortOptions, $allowedLongOptions);

// check for errors and die with an error message if there was a problem

if (PEAR::isError($ret)) {

    die (
"Error in command line: " . $ret->getMessage() . "\n");

}

// now parse the options array

$opts = $ret[0];

if (
sizeof($opts) > 0) {

    // if at least one option is present

    // iterate over option list

    // assign option values to PHP variables

    
foreach ($opts as $o) {

        switch (
$o[0]) {

            case
'h':

            case
'--host':

                $host = $o[1];

                break;

            case
'u':

            case
'--user':

                
$user = $o[1];

                break;

            case 'p':

            case
'--pass':

                
$pass = $o[1];

                break;

            case
'd':

            case '--db':

                
$db = $o[1];

                break;

        }

    }

}

else {

    die(
"Error: No database connection parameters specified\n");

}

// open connection

$connection = mysql_connect($host, $user, $pass) or die ("Unable to connect!\n");

if (empty($db)) {

    // if no database was specified, get database list

    
$query = "SHOW DATABASES";

    
$result = mysql_query($query) or die ("Error in query: $query. " . mysql_error() . "\n");

    while ($row = mysql_fetch_array($result)) {

        
$dbs[] = $row[0];

    }

}

else {

    
// if a database was specified

    mysql_select_db($db) or die ("Error in database selection: " . mysql_error() . "\n");

    
$dbs[] = $db;

}

foreach ($dbs as $db) {

    // for each database

    // print database name

    
echo strtoupper($db) . ": ";

    
// get table list

    
$query2 = "SHOW TABLES FROM " . $db;

    
$result2 = mysql_query($query2) or die ("Error in query: $query2. " . mysql_error());

    // print table names

    
while ($row2 = mysql_fetch_array($result2)) {

        echo
" " . $row2[0];

    }

    echo
"\n";

}

// close connection

mysql_close($connection);

?>

Most of this should be familiar to you from the earlier examples.
The script can accept the host name, user name, password and an
optional database name in either short or long format, and use that
information to open up a connection to the specified MySQL server and
list the tables and databases found there.

Here are some usage examples:

shell> php script.php --host=localhost --user=root --pass=rightpass --db=test
TEST: attributes colors customers departments dummy employees groups invoices
shell> php script.php -hlocalhost --user=root -pwrongpass
Warning: Access denied for user: 'root@127.0.0.1' (Using password: YES) in script.php
Warning: MySQL Connection Failed: Access denied for user: 'root@127.0.0.1' (Using password: YES) in script.php
Unable to connect!
shell> php script.php -hlocalhost --user=root -prightpass
FKTEST: articles departments employees states votes
JABBER: departments employees payroll
LIBRARY: members status videos
MYSQL: columns_priv db func host tables_priv user
PHPBANNER: banner_client banner_data country
TEST: attributes colors customers departments dummy employees groups invoices


Summary

Over the course of this tutorial, you have been introduced to one of the
more interesting packages in PEAR: the Console_Getopt package. This module
provides a simple API to parse options passed to your PHP scripts from the
command-line, and use them to modify the behaviour of your PHP program.

If you’d like to read more about this package, visit the package homepage
and online documentation at http://pear.php.net/package/Console_Getopt.


About the Author

Vikram Vaswani is the founder and CEO of Melonfire, a production house and consultancy based
in Bombay, India. He has written several tutorials for zend.com, including a complete
PHP beginners’ series, part of which is due to
be published in book format by McGraw-Hill in January 2005.


Copyright Melonfire, 2004 (http://www.melonfire.com). All rights reserved.

,

Comments are closed.