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
This 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: '[email protected]' (Using password: YES) in script.php
Warning: MySQL Connection Failed: Access denied for user: '[email protected]' (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
End Game
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.
This article was first published on 30 Jul 2004.