Piping to PHP

This blog post is more than 6 years old, so the content may be out of date.

Pipes are cool, super-uber cool. Simple, elegant, and opens up more functionality than you can shake a stick at.

Unfortunately they're rather unused in the PHP community.

Answers like this Ubuntu forums thread suggest using CLI arguments instead:

i want to be able to do something similar but instead of grep, with my php script, for instance:

ls | grep .jpg | myscript.php

you could then use a "dir" in php with the command line argument to pull your ls instead, just call it from the command line in the folder you're wanting it pulled from like this:

myscript.php .jpg

I.e. The answer was "Don't use pipes".

The flexibility of pipeline composition is so great, it makes no sense to ignore. If the answer is "write everything in PHP", you're doing it wrong.

It's very simple to accept piped data to PHP:

$data = '';
while ($input = fread(STDIN, 1024)) {
  $data .= $input;
}

My use case was related to Drush - I have a Drush hook which allows XML content to be imported, creating article nodes. I wanted to support two modes of operation: provide a filename as a command-line argument, or pipe the XML directly to Drush.

drush importarticle foo.xml
cat myarticle.xml | sed 's/foo/bar/' | drush importarticle

The problem is that reading stdin is usually blocking, which means if content isn't piped, the script hangs, waiting for command-line input. However, it's trivial to set this to non-blocking (using stream_set_blocking).

Here's the finished version:

/**
 * Drush command to import an article.
 */
function foo__drush__import_article() {
  // Attempt to check for piped input.
  if ($data = _foo__drush__get_piped_input()) {
    // Assume piped data is a single article.
    mymodule_import_article($data);
  }
 
  // Alternatively, check for filenames provided as CLI arguments.
  elseif (count(func_get_args())) {
    $errors = array();
    $files = func_get_args();
 
    // Validate each file exists, and exit before importing if any are missing.
    foreach ($files as $file) {
      if (!is_file($file)) {
        $errors[] = $file;
      }
    }
    if (count($errors)) {
      $msg = dt('Import error: File not found: @files', array('@files' => implode(', ', $errors)));
      return drush_set_error('DRUSH_FOO_FILE_NOT_FOUND', $msg);
    }
 
    // Attempt to import each file.
    foreach ($files as $file) {
      $data = file_get_contents($file);
      mymodule_import_article($data);
    }
  }
}
 
 
/**
 * Check for piped input.
 *
 * @return String
 * Data piped to Drush.
 */
function _foo__drush__get_piped_input() {
  static $data = NULL;
  if (is_null($data)) {
    $data = '';
    stream_set_blocking(STDIN, FALSE);
    while ($input = fread(STDIN, 1024)) {
      $data .= $input;
    }
  }
  return $data;
}

Blog tags:

Comments

Cool beans!

Thank you !

Add new comment

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <apache>, <bash>, <c>, <cpp>, <drupal5>, <drupal6>, <java>, <javascript>, <php>, <python>, <ruby>. The supported tag styles are: <foo>, [foo]. PHP source code can also be enclosed in <?php ... ?> or <% ... %>.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.