Section 16.2. PHP CLI Shell Scripts

16.2. PHP CLI Shell Scripts

The CLI version of PHP is meant for writing standalone shell-scripts running independently from any web server. As of PHP 4.3.0, the CLI version of PHP is installed by default, alongside whatever web server interface you choose to install.

It has been possible to write shell scripts using the CGI version of PHP since PHP 3.0, but a number of workarounds had to be added to make CGI better suited for this, such as the q option to silence headers. During PHP 4's development, it became apparent that a separate command-line version of PHP was needed to keep CGI clean, and CLI has been distributed since 4.2.0.

This has not stopped people from writing PHP shell scripts, but CLI is more accessible (because it is always installed) and consistent (it's designed for this job).

16.2.1. How CLI Differs From CGI

The CLI version of PHP is quite similar to the CGI version, upon which it was once based. The main difference lies in all the web server integration, which is really what CGI is about. With CLI, PHP is trimmed down to the very basics, and imports no GET or POST form variables, outputs no MIME headers in the output, and generally does none of the behind-the-scenes that other SAPI implementations do.

The CLI version of PHP behaves like any other script parser, such as Perl or Python. The one remaining proof of PHP's web heritage is the fact that you still need to use the <?php ?> tags around code. Default Parameters

CLI has different default values for a few command-line options and php.ini settings, as shown in Table 16.1.

Table 16.1. CLI Default Options




-q option


Suppresses HTTP headers in output.

-C option


PHP does not change its working directory to that of the main script.



Error messages from PHP will be in plain text rather than HTML.

implicit _flush





The $argc and $argv global variables are registered regardless of the register_argc _arg settings in php.ini.



The longest time (in seconds) PHP lets scripts execute; 0 means no limit.

CLI DefaultDescription Extra Options

There are some command-line options in PHP CLI that CGI does not offer, as shown in Table 16.2.

Table 16.2. Extra CLI Options


CLI Default


-r code


Run code as PHP code (no <?php necessary).

-R code


Run code as for every line on stdin.

_B code


Run code before processing lines with -R or -F.

-E code


Run code after processing lines with -R or -F.

_F file


Execute file for every input line.

These options can be used to quickly execute some PHP code from the command line; for example:

 $ php r 'var_dump(urlencode("œøå"));' 

When using r, -R, -B, and -E, make sure that your PHP code is complete with the final semicolon. php.ini Name and Location

On UNIX-like systems, PHP (with back-ends other than CLI) looks for php.ini in /usr/local/lib by default. To be more "shell-ish," the CLI back-end looks for /etc/php-cli.ini by default, instead. This makes it possible to keep separate php.ini files for your web server and CLI/shell scripts, without having to specify the c option every time you run a PHP-driven script.

Different UNIX/Linux distributions that bundle PHP often use their own default php.ini location; you can find the file used by your PHP executable with get_cfg_var("cfg_file_path"). Other Differences

When PHP is running inside a web server, functionality, such as fork() makes little sense, because it would duplicate the entire web-server process and not just PHP. This is bad because the web server process contains lots of code that is completely unrelated to PHP, possibly including other web-scripting modules, such as mod_perl. In a threaded environment, it would even duplicate all the threads in that process. If the purpose of your fork is to exec another program right away, this does not matter. But if you want to fork to keep running PHP code in the new process, having this extra baggage in the process can be really bad.

For this reason, PHP's process control extension (pcntl) is only available in the CLI version, where a fork() call only makes a duplicate of PHP.

16.2.2. The Shell-Scripting Environment

The CLI PHP script operates differently in its environment compared to its web-server embedded counterpart. Shell scripts are running in their own process, containing PHP and nothing else. Inside a web server, PHP shares the process with the web server itself and any other modules the web server may have loaded. The web server environment has many restrictions because of this. For example, who gets standard input? What about signals, and what happens if you fork (duplicate) the process? Usually all of these types of resources are managed by the hosting web server. User Input

If you need user input in a PHP shell script, you should use standard input, which is available in the PHP stream STDIN or the "terminal typewriter" device on UNIX flavors /dev/tty.

 <?php print "What is your one purpose in life? "; $purpose = trim(fgets(STDIN)); ?> 

If you are writing a script that needs to read from standard input as well as read user input from the terminal, you must use /dev/tty for user interaction. On Windows, you can't read from STDIN at the same time as when you reading from the terminal. Execution Lifetime

When embedded in a web server, PHP scripts usually do their job quickly and exit. This paradigm does not fit when using CLI; your scripts may run forever, or at least until the next power failure. For example, if you write a daemon (UNIX lingo for a server process running in the background), the script will typically hang around forever, waiting for some kind of input to process, a timer signal, or something similar.

One of the practical consequences of this is that sloppy coding styles, which are relatively harmless in a short web-server request, have more of an impact in a long-running script. For example, when you open a file or database connection but don't explicitly close it, PHP closes it for you at the end of the request. But in a long-running script, "at the end of the request" is not until the script exits, which it does not even have to do.

This does not have to be a problem, because PHP also frees resources when they are no longer referenced. But keep this in mind when programming scripts that are supposed to run for some time. If you are finished with a file, close the file descriptor. If you're finished with database operations, disconnect. If you don't need that big array anymore, empty it. Hash-Bang Whiz-Blam

On UNIX-like systems, if the first two characters of an executable file are "#!" (called hash-bang), the rest of the line is treated as the name of the program executing the file. The specified program is invoked with the script's name as the first parameter, followed by the parameters given to the script itself.

Let's say you make a PHP script called "myreport," which starts like this:

 #!/usr/bin/php -Cq <?php require_once "DB.php"; $db = DB::connect("mysql://.... 

First, ensure that the script is executable, like this:

 $ chmod +x myreport 

Then, when you run myreport traffic, your shell first searches for myreport in the directories listed in its PATH environment variablesay it is located in the /usr/local/bin directory.

When the shell finds it there, it tells the operating system to execute this program. The OS then opens the file, discovers the #! characters, and re-executes the process as

 /usr/bin/php -Cq /usr/local/bin/myreport traffic. 

When PHP finally starts, it imports ./myreport and TRaffic into the $argv array, and then executes your script.

Note that because the shell searched your PATH to find the actual location of myreport, which the OS then used when executing PHP, $argv[0] will contain the full path to myreport. If you had specified a relative path, such as ../bin/myreport, the shell would not have searched PATH and $argv[0] would also become ../bin/myreport.

16.2.3. Parsing Command-Line Options

Command-line options are used in UNIX to specify alternate behavior or additional parameters for commands. You spot them by the leading dash. Here are some examples:

 $ ls ltr $ rm f junk 

Usually, options are located before regular parameters (that do not start with a dash) on the command line. Some commands, such as cvs or pear, have additional subcommands accepting their own set of options. The PEAR installer is one such command.

There is no getopt function built into PHP, but PEAR offers a package called Console_Getopt that supports both short and long (GNU-style) options. Console_Getopt is bundled with PHP and is installed by default unless you explicitly disable PEAR.

Here is a command-line script accepting four short options: -v and q and increasing or decreasing verbosity level, -h for displaying help, or c for setting another configuration file:

 #!/usr/bin/php <?php require_once "Console/Getopt.php"; $verbose = 1; $config_file = $_ENV['HOME'] . '/.myrc'; $options = Console_Getopt::getopt($argv, 'hqvc:'); foreach ($options[0] as $opt) {     switch ($opt[0]) {         case 'q':             $verbose--;             break;         case 'v':             $verbose++;             break;         case 'h':             usage();             exit;         case 'c':             $config_file = $opt[1];             break;     } } if ($verbose > 1) {     print "Config file is \"$config_file\".\n"; } // rest of the script code goes here function usage() {     $stderr = fopen("php://stderr", "w");     $progname = basename($GLOBALS['argv'][0]);     fwrite($stderr, "Usage: $progname [-qvh] [-c config-file] Options:    -q         be less verbose    -v         be more verbose    -h         display help    -c <file>  read configuration from <file> ");     fclose($stderr); } ?> 

First, the script includes the Console_Getopt class definition. After setting default values for $verbose and $config_file, the getopt() call is accomplished with the parameter list and a string specifying which options are accepted.

Take a look at the option specification string. Each alphanumeric character in the option specification string is a valid option. If the option character is followed by a colon, the option is expected to have a value. In the previous example, c: says that the c option expects a parameter, which is the configuration file to use. The q, -v, and h options don't have any following special characters, so they are simple flag/toggle-type options.

The getopt() method returns an array of the form array(array(option, value), ...). The foreach loop iterates through this array, and $opt is assigned to the array(option, value). For flag options, the value will always be NULL (no need to check because you already know which options are plain flags), while for options taking parameters, the second element in this array is the actual parameter. For example, -c foo would give array('c', 'foo') in $foo. It is possible to treat the same option as many times as needed. In this example, the verbosity level of the program increases by 1 each time the v option is used. If the user specifies -vvvvv to it, the verbosity level will be increased 5 times.

It is also possible to specify that an option parameter is optional by using two colons instead of onefor example, c::. When encountering an option parameter that is not mandatory, Console_Getopt uses the remains of the option as the option parameter value. For example, if the c option was specified with c::, the option string would give the option parameter value, but just -c would be allowed, too. However, when an option parameter becomes optional, -c foo is no longer allowed; it has to be -cfoo.

Following is the same example supporting both short- and long-style options:

 #!/usr/bin/php <?php require_once "Console/Getopt.php"; $verbose = 1; $config_file = $_ENV['HOME'] . '/.myrc'; $options = Console_Getopt::getopt($argv, 'hqvc::',                                   array('help', 'quiet', 'verbose', 'config=')); foreach ($options[0] as $opt) {     var_dump($opt);     switch ($opt[0]) {         case 'q': case '--quiet':             $verbose--;             break;         case 'v': case '--verbose':             $verbose++;             break;         case 'h': case '--help':             usage();             exit;         case 'c': case '--config':             $config_file = $opt[1];             break;     } } if ($verbose > 1) {     print "Config file is \"$config_file\".\n"; } // rest of the script code goes here function usage() {     $stderr = fopen("php://stderr", "w");     $progname = basename($GLOBALS['argv'][0]);     fwrite($stderr, "Usage: $progname [options] Options:    -q, --quiet                 be less verbose    -v, --verbose               be more verbose    -h, --help                  display help    -c <file>, --config=<file>  read configuration from <file> ");     fclose($stderr); } ?> 

16.2.4. Good Practices

When writing shell scripts, you should follow some good practices to make life easier for yourself and others who will use your script.

For example, most UNIX users expect their programs to respond to foo h or foo --help with a brief usage message, or that they print errors on standard error instead of standard output. This section lists some practices that the authors consider Good™. Usage Message

After using UNIX/Linux for a while, you get used to being able to type command help or command h for a brief description of a command's option and general usage. Most UNIX users expect their program to respond to these options.

Display a usage message on standard error and exit with a non-0 code if the script is started without the expected parameters, or if it runs with the -h option (--help if you are using long options). The usage message should list all the required and optional parameters, and could look something like this:

 Usage: myscript [options] <file...> Options:      -v, --version     Show myscript version      -h, --help        Display this help text      -d dsn, --dsn=dsn Connect to database "dsn" 

There is a standard notation for options and parameters as well:

 [-c]              May have c. {-c foo}          Must have c with a parameter. [-abcdef]         May have any of a ... f. [-a | -b]         May have either a or b. {-a | -b}         Must have either a or b. <file>            Must have file as a parameter (not option). <file...>         Must have 1+ file parameters. [file...]         May have 1+ file parameters. 

If your program accepts only a few options, you should list them on the first line of the usage message, like this:

 Usage: myscript [-vh] [-d dsn] <file...> Options:      -v, --version     Show myscript version      -h, --help        Display this help text      -d dsn, --dsn=dsn Connect to database "dsn" Exit Code

If the script fails, exit with a non-0 code (except 255, which is reserved by PHP itself for compile/parse errors). If the script does not fail, exit with code 0.

Be aware that earlier PHP versions (pre-4.2) had a bug in the exit code handling. Exiting in any other way than letting the script finish results in a "non-true" exit code. Error Messages

Prepend the script name to all error messages, so the user can see from which script the error originates. This is useful if the script is invoked from within other scripts or programs so you can see from which program the error originates.

If you base your error messages on the PEAR error handling, you can set this up in fire-and-forget mode, like this:

 $progname = basename($argv[0]); PEAR::setErrorHandling(PEAR_ERROR_DIE, "$progname: %s\n"); 

Here, unless another error handler explicitly overrides the default one, all uncaught PEAR errors will cause the script to die after printing programname: error message. You can keep coding in the script, resting assured that if there is an error, the default handler will catch it, display the message, and exit, and you don't have to litter your code with error checks.

16.2.5. Process Control

When running PHP scripts in CLI, the pcntl extension provides functions for controlling the PHP process. If PHP is embedded in a web server or somewhere else, process control is left to the embedding environment and pcntl is disabled. Processes

A process is a piece of code executed by the operating system. On UNIX, processes consist of executable code, environment variables, stack memory, heap (dynamically allocated) memory, file descriptors, and security properties such as user id.

When executing a PHP script, the php process's executable code is the php binary itself (for example, /usr/local/bin/php). The script is stored in heap memory, although both heap and stack memory are used during script execution. Forking

Forking is UNIX lingo for making a new process by duplicating an existing one. The duplicate (child) process inherits code, environment, memory (copy on write), file descriptors, and everything from the parent process. Often, you either immediately replace the guts of the process by executing another executable program, or close inherited file descriptors and prepare the child process for its job:

 <?php $child_pid = pcntl_fork(); if ($child_pid == -1) {     die("pcntl_fork() failed: $php_errorstr"); } else if ($child_pid) {     printf("I am the parent, my pid is %d and my child's pid is %d.\n",            posix_getpid(), $child_pid); } else {     printf("I am the child, my pid is %d.\n", posix_getpid()); } ?> 

This example demonstrates forking, creating a duplicate of the initial process. Both processes continue running the current script from the line after the fork. The difference is that in the parent process, the fork call returned the process id of the child process, while in the child process the fork call returned 0. This is how you distinguish the creating and created processes.

If pcntl_fork() returns 1, an error occurred and no process was created. Exec

When one program runs another program, the execution of the second program is actually a two-step procedure. First, the calling process forks and makes a duplicate of itself, and then immediately does an exec call to replace the executable code and memory with that of the new program.

If you just want to run a program and read the output or write to it, there are easier ways of doing it, such as popen(). But, if you must be able to both read and write to the program, you need to manually fork and exec from PHP, or use the proc_open() function.

Following is an example that forks and execs an ls command:

 <?php $child_pid = pcntl_fork(); if ($child_pid == 0) {     // replace php with "ls" command in child     pcntl_exec("/bin/ls", array("-la")); } elseif ($child_pid != -1) {     // wait for the "ls" process to exit     pcntl_waitpid($child_pid, $status, 0); } ?> 

First, a child process is created. Then, in the process where $child_pid was returned as 0 (the child process), the ls command is executed. The output from ls will go to standard output. The parent process waits for the child to exit before it continues.

Here is another example. PHP detaches itself from the terminal and continues running in the background (a technique known as daemonizing):

 <?php $pid = pcntl_fork(); if ($pid) {     exit(0); } // create new session, detach from shell's process group posix_setsid(); // XXX if STD{IN,OUT,ERR} constants become available, these have // to be closed here. while (true) {     error_log("heartbeat\n", 3, "/tmp/test.log");     sleep(10); } ?> 

First, this script forks and creates a second PHP process. The parent process then exits, and the child continues. Then, the child disconnects from the controlling terminal and creates its own session and process group with posix_setsid(). This makes sure that signals sent to the shell are not passed along to the child PHP process. Signals

In UNIX, signals are a basic mechanism to pass messages between processes. They enable processes to tell each other that some type of event has just occurred. This type of event is the only information passed to basic UNIX signal handlers. There is another signal-handling mechanism called "sigaction" in which signal handlers receive more information, but PHP signals are based on the former, basic form. For example, if the user presses Ctrl-c to stop a command-line program, the program receives an interrupt signal, called SIGINT.

In PHP, you can set up a function to handle one or more signals with the pcntl_signal() function, like this:

 <?php function sigint_handler($signal) {      print "Interrupt!\n";      exit; } pcntl_signal(SIGINT, "sigint_handler"); declare (ticks = 1) { while (sleep(1)); } ?> 

This script sleeps until you terminate it. If you do press Ctrl-c, it prints Interrupt! and exits. You could change this example to ignore Ctrl-c completely by changing the signal-handler function to the predefined SIG_IGN:

 pcntl_signal(SIGINT, SIG_IGN); 

You may change a signal handler anytime, including inside a signal-handling function. To revert to the default signal handler, use SIG_DFL:

 pcntl_signal(SIGINT, SIG_DFL); 

PHP probably supports all the signals your system supports. Try typing kill l in your shell to see some. Table 16.3 lists of signals that may be useful from PHP, either catching and handling them, or sending them to (killing) other processes.

Table 16.3.




Hangup. Used to notify when terminal connection is lost.


Interrupt. Send when user hits the interrupt (Ctrl-c) key.


Sent by the abort() C function; used by assert().


Non-graceful termination of the process; cannot be caught.


User-defined signal 1.


Segmentation fault; in some operating systems, it's known as General Protection Failure.


User-defined signal 2.


Sent when a pipe the process is reading closes unexpectedly.


Sent when an alarm times out.


Terminate process normally.


A child process just died or changed status.


Continue after stopping with SIGSTOP.


Halt process; cannot be caught.


Halt process; may be caught.


Process stopped due to tty input.


Process stopped due to tty output.


CPU time limit exceeded.


File size limit exceeded.


Passed when a baby is ready to change diapers, hungry, about to climb something dangerous or doing anything else that requires immediate attention from a parent PHP programmer.

16.2.6. Examples

Here are some examples of command-line tools written in PHP. PHP Filter Utility

This example includes a little tool for filtering line by line from standard input through a PHP function that returns a string:

 #!/usr/bin/env php <?php if (empty($argv[1])) {     die("Usage: phpfilter <function>\n"); } $function = $argv[1]; while ($line = fgets(STDIN)) {     $out = $function($line);     if (!preg_match('/\n\r*$/', $out)) {         $out .= "\n";     }     print $out; } 


This example reads line by line from STDIN, which is a pre-defined file resource in PHP for standard input. An extra newline is added in case the PHP function stripped away the newline. Try it with base64_encode:

 $ ls | phpfilter base64_encode QnVpbGRpbmdfUEVBUl9Db21wb25lbnRzLwkJICAgUGVyZm9ybWFuY2UvCg== Q2hhcHRlciAxMyAtIEJ1aWxkaW5nIFBFQVIgQ29tcG9uZW50cy56aXAgIHJldmlld3Mv g== RGF0YWJhc2VzLwkJCQkgICBTaGVsbF9TY3JpcHRpbmcvCg== RXJyb3JfSGFuZGxpbmcvCQkJCSAgIHRtcC8K SW1wb3J0YW50X1BFQVJfUGFja2FnZXMvCQkgICBVc2luZ19QRUFSLwo= 

The final example is a simple chat server. It handles many simultaneous users, does buffering of input and output, may run as a daemon, and has three commands: /who, /quit, and /shutdown.

Connect to it with a telnet program; it uses port 1234 by default. To log out, type /quit; to see what users are on type /who; type /shutdown to take the server down.

You may change the port number with the p option, or the maximum number of simultaneous users with the m option. Try the h option for help:

 <?php error_reporting(E_ALL); require_once "PEAR.php"; require_once "Console/Getopt.php"; $DAEMON = false; $PORT = 1234; $MAX_USERS = 50; $progname = basename($argv[0]); PEAR::setErrorHandling(PEAR_ERROR_DIE, "$progname: %s\n"); $options = Console_Getopt::getopt($argv, "dp:m:h"); foreach ($options[0] as $opt) {     switch ($opt[0]) {         case 'd':             $DAEMON = true;             break;         case 'p':             $PORT = $opt[1];             break;         case 'm':             $MAX_USERS = $opt[1];             break;         case 'h':         case '?':            fwrite(STDERR, "Usage: $progname [-dh] [-p port] [-m users] Options:     -d        detach into background (daemon mode)     -p port   set tcp port number     -m users  set max number of users     -h        this help message ");             exit(1);     } } if ($DAEMON) {     $pid = pcntl_fork();     if ($pid) {         exit(0);     }     posix_setsid(); } $sock = socket_create_listen($PORT); if (!$sock) {     exit(1); } $shutting_down = false; $connections = array(); $usernames = array(); $input = array(); $output = array(); $close = array(); while (true) {     $readfds = array_merge($connections, array($sock));     $writefds = array();     reset($output);     while (list($i, $b) = each($output)) {         if (strlen($b) > 0) {             $writefds[] = $connections[$i];         }     }     if (socket_select($readfds, $writefds, $e = null, 60)) {         foreach ($readfds as $rfd) {             if ($rfd == $sock) {                 $newconn = socket_accept($sock);                 $i = (int)$newconn;                 $reject = '';                 if (count($connections) >= $MAX_USERS) {                     $reject = "Server full. Try again later.\n";                 } elseif ($shutting_down) {                     $reject = "Server shutting down.\n";                 }                 $connections[$i] = $newconn;                 $output[$i] = '';                 if ($reject) {                     output($i, $reject);                     $close[$i] = true;                 } else {                     output($i, "Welcome to the PHP Chat Server!\n");                     output($i, "Username: ");                 }                 $usernames[$i] = "";                 $input[$i] = "";                 continue;             }             $i = (int)$rfd;             $tmp = @socket_read($rfd, 2048, PHP_NORMAL_READ);             if (!$tmp) {                 broadcast($usernames[$i] . " lost link.\n");                 print "connection closed on socket $i\n";                 close($i);                 continue 2;             }             $input[$i] .= $tmp;             $tmp = substr($input[$i], -1);             if ($tmp != "\r" && $tmp != "\n") {                 // no end of line, more data coming                 continue;             }             $line = trim($input[$i]);             $input[$i] = "";             if (empty($line)) {                 continue;             }             if (empty($usernames[$i])) {                 if (strlen($line) < 2) {                     output($i, "Username must be at least two characters.\n");                 } else {                     $user = substr($line, 0, 16);                     $f = array_search($user, $usernames);                     if ($f !== false) {                         output($i, "That user name is taken, try another.\n");                     } else {                         $usernames[$i] = $user;                         output($i, "You are now known as \"$user\".\n");                         broadcast("$user has logged on.\n", $i);                         continue;                     }                 }             }             if (empty($usernames[$i])) {                 output($i, "Username: ");             } else {                 if (strtolower($line) == "/quit") {                     output($i, "Bye!\n");                     broadcast("$usernames[$i] has logged off.", $i);                     $close[$i] = true;                 } elseif (strtolower($line) == "/shutdown") {                     $shutting_down = true;                     broadcast("Shutting down. See you later.\n");                 } elseif (strtolower($line) == "/who") {                     output($i, "Current users:\n");                     foreach ($usernames as $u) {                         output($i, "$u\n");                     }                 } else {                     $msg = '['.$usernames[$i].']: '.$line."\n";                     broadcast($msg, $i);                     output($i, ">>> $line\n");                 }             }         }         foreach ($writefds as $wfd) {             $i = (int)$wfd;             if (!empty($output[$i])) {                 $w = socket_write($wfd, $output[$i]);                 if ($w == strlen($output[$i])) {                     $output[$i] = "";                     if (isset($close[$i])) {                         close($i);                     }                 } else {                     $output[$i] = substr($output[$i], $w);                 }             }         }     }     if ($shutting_down) {         $may_shutdown = true;         foreach ($output as $i => $o) {             if (strlen($o) > 0) {                 print "shutdown: still data on fd $i\n";                 $may_shutdown = false;                 break;             }         }         if ($may_shutdown) {             print "shutdown complete\n";             socket_shutdown($sock);             socket_close($sock);             exit;         }     } } function output($user, $msg) {     global $output;     settype($user, "int");     $tmp = substr($msg, -2);     if ($tmp{1} == "\n" && $tmp{0} != "\r") {         $msg = substr($msg, 0, -1) . "\r\n";     }     $output[$user] .= $msg; } function broadcast($msg, $except = null) {     global $output, $connections, $usernames;     foreach ($connections as $i => $r) {         if (empty($usernames[$i])) {             // don't send messages to users who have not logged on yet continue;         }         if (!$except || $except != $i) {             output($i, $msg);         }     } } function close($i) {     global $connections, $input, $output, $usernames, $close;     socket_shutdown($connections[$i]);     socket_close($connections[$i]);     unset($connections[$i]);     unset($input[$i]);     unset($output[$i]);     unset($usernames[$i]);     unset($close[$i]); } ?> 

    PHP 5 Power Programming
    PHP 5 Power Programming
    ISBN: 013147149X
    EAN: 2147483647
    Year: 2003
    Pages: 240

    Similar book on Amazon © 2008-2017.
    If you may any questions please contact us: