Christoph Hochstrasser

PHP Unix Programming

Ever worried that your scripts ain’t good Unix citizens? Here are some quick, practial recipes usable for everyday PHP Command Line scripting.

Use appropiate exit codes

It’s important to respect the Exit Code Conventions of Unix, so other programs can see if the command ran successfully and then can take action.

PHP provides the exit function to stop script execution and return the code passed as first argument as Exit Code back to the system.

Here’s a quick overview of appropiate Exit Codes:

  • The Exit Code is an Integer Value between 0 and 255.
  • Use 0 to indicate that the command ran successfully. In PHP this can be done by calling exit(); or exit(0);.
  • Exit Codes greather than 1 are used to signal that your command failed in some way.
  • Codes between 128 and 255 are reserved for special purposes. Do not use them.

The Appendix D. of the Advanced Shell Scripting Guide is a good place to look for infos about Exit Codes.

Cleanup Your Mess

It’s always good to cleanup the mess that you’ve created. In a Shell Script you would trap the EXIT signal:

#!/bin/sh

my_exit_handler() {
    echo "Cleaning up..." >&2
}

trap my_exit_handler EXIT

You can do the same in PHP by using register_shutdown_function and passing a valid callback:

#!/usr/bin/env php
<?php

register_shutdown_function(function() {
    fwrite(STDERR, "Cleaning up...\n");
});

Writing Error Output

The Error Pipe is available via the STDERR constant. Simply use fwrite to write to it:

#!/usr/bin/env php
<?php

fwrite(STDERR, "Some error happened,\n");
exit(1);

To make it easy to spot error output of your command in a log file, the convention is to prepend your command’s name in front of the error output followed by a colon:

#!/usr/bin/env php
<?php

fwrite(STDERR, "foo: Some error happened.\n");
exit(1);

Later on you can use grep foo: to show only log entries related to your command.

Read Input piped-in from other commands

When data is piped into a script then PHP makes the input stream accessible via the STDIN constant. You can use stream_get_contents to get all input data:

#!/usr/bin/env php
<?php

$input = stream_get_contents(STDIN);

It’s important to note that stream_get_contents(STDIN) blocks script execution until data becomes available on STDIN.

To check if data is available we can use stream_select. stream_select receives three lists of stream resources: “read”, “write” and “except”. To see if data is available to read we put the stream into the “read” list:

#!/usr/bin/env php
<?php

$read = array(STDIN);
$write = null;
$except = null;

Then we pass these variables to the stream_select function:

# ...

$readyCount = stream_select($read, $write, $except, 0);

if ($readyCount > 0 and $read) {
    # Something is available to read on STDIN.
    $input = stream_get_contents(STDIN);
}

stream_select modifies the $read list to contain only the resources which have data available for reading. When nothing can be read, then the $read variable will contain no resources.

Usually it’s better to switch to receiving input from the input stream if a special parameter is passed, for example -:

#!/usr/bin/env php
<?php

$file = @$_SERVER["argv"][0];

if (!$file) {
    fwrite(STDERR, usage());
    exit(1);
}

# Read from STDIN when "-" is passed as file name:
if ($file === "-") {
    $input = stream_get_contents(STDIN);
} else {
    $input = file_get_contents($file);
}

# Do something with the $input

Fin

That’s it for now. If you know some more tips just let me know by mentioning @hochchristoph on Twitter.