sábado, 7 de agosto de 2010

Ejecutando comandos sobre un programa en ejecución y capturando sus salidas (en PHP)

En este artículo les dejo un código que me causo varios dolores de cabeza, para el que tuve que hacer un montón de pruebas hasta lograrlo.

Para mi proyecto phpautoconf estaba necesitando, en PHP, abrir un proceso (programa en ejecución), ejecutar comandos y tras cada uno, capturar su salida. Parece sencillo, pero el problema es que PHP por defecto maneja los flujos de forma bloqueante, según lo explicado aquí, e intentar manejarlos de forma diferente requiere de algunas "cosillas interesantes".

La solución:

<?php
// file: proc.php

// proc_open parameters
$descriptorspec = array(
   0 => array("pipe", "r"),  // stdin is a pipe that the child will read from    1 => array("pipe", "w"),  // stdout is a pipe that the child will write to    2 => array("pipe", "w") // stderr is a file to write to
); $cwd = '/tmp'; $process = proc_open('bash', $descriptorspec, $pipes, $cwd); // stream_select parameters (waits until something can be read from pipe) $read = array($pipes[1]); $write = null; $except = null; $readTimeout = 5; if (is_resource($process)) { stream_set_blocking($pipes[1], FALSE); fwrite($pipes[0], "echo 'hola'\n"); fflush($pipes[0]); stream_select($read, $write, $except, $readTimeout); $output = fgets($pipes[1]); echo "output = $output\n"; fwrite($pipes[0], "echo 'chao'\n"); fflush($pipes[0]); stream_select($read, $write, $except, $readTimeout); $output = fgets($pipes[1]); echo "output = $output\n"; fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]); $return_value = proc_close($process); echo "return_value = $return_value\n"; } ?>

Las "cosillas interensantes":
  1. stream_set_blocking($pipes[1], FALSE);. Define la tubería de salida como no bloqueante. En términos prácticos, permitirá obtener la salida sin necesidad de cerrar el flujo (fclose).
  2. fflush($pipes[0]). Libera el flujo de entrada (flush).
  3. \n al final de cada comando escrito (fwrite). Le indica al proceso, en este caso la consola bash, que finalizó el comando.

Como dato interesante, conseguí en varios foros que mucha gente utilizaba la función sleep entre escritura y lectura para dar tiempo a que se llenara el flujo de salida. Sugiero mejor utilizar la función stream_select, que espera un máximo de tiempo por la respuesta, pero si ésta se recibe antes, se libera el bloqueo. Para ejecutar:

$ php proc.php


4 comentarios:

  1. Hi
    Please can you help me with the following problem. I need to run a program (cc+ program, console mode, vs2008) send values to it and cath the output?
    Thank you

    ResponderEliminar
  2. Mário, good to say hello. In C++ I really don't know exactly how you can do something like this, but I'm sure there is plenty information about that on the web.

    I remember from my C days you can do things like that using system function, but there have to exists an specialized API that I just don't know.

    Regards,

    ResponderEliminar
  3. Muchas gracias! Ayudar me mucho (This helps me a lot!)

    ResponderEliminar
  4. I'm very glad to read this Amaury. Helping others with same technical issues is the purpose of this blog.

    ResponderEliminar