2011-05-16 12 views
9

El problema

que estaba usando una función que hace uso de proc_open() para invocar comandos shell. Parece que la forma en que estaba haciendo STDIO era incorrecta y algunas veces causaba que PHP o el comando objetivo se bloqueen. Este es el código original:ejecución shell Proper en PHP

function execute($cmd, $stdin=null){ 
    $proc=proc_open($cmd,array(0=>array('pipe','r'),1=>array('pipe','w'),2=>array('pipe','w')),$pipes); 
    fwrite($pipes[0],$stdin);    fclose($pipes[0]); 
    $stdout=stream_get_contents($pipes[1]); fclose($pipes[1]); 
    $stderr=stream_get_contents($pipes[2]); fclose($pipes[2]); 
    return array('stdout'=>$stdout, 'stderr'=>$stderr, 'return'=>proc_close($proc)); 
} 

Funciona mayor parte del tiempo, pero eso no es suficiente, quiero que funcione siempre.

El problema radica en stream_get_contents() encerrar si las memorias intermedias Stdio exceden 4k de datos.

caso de prueba

function out($data){ 
    file_put_contents('php://stdout',$data); 
} 
function err($data){ 
    file_put_contents('php://stderr',$data); 
} 
if(isset($argc)){ 
    // RUN CLI TESTCASE 
    out(str_repeat('o',1030); 
    err(str_repeat('e',1030); 
    out(str_repeat('O',1030); 
    err(str_repeat('E',1030); 
    die(128); // to test return error code 
}else{ 
    // RUN EXECUTION TEST CASE 
    $res=execute('php -f '.escapeshellarg(__FILE__)); 
} 

Nos salida de una cadena doble en STDERR y stdout con la longitud combinada de 4120 bytes (superiores a 4k). Esto hace que PHP se bloquee en ambos lados.

Solución

Al parecer, stream_select() es el camino a seguir. Tengo el siguiente código:

function execute($cmd,$stdin=null,$timeout=20000){ 
    $proc=proc_open($cmd,array(0=>array('pipe','r'),1=>array('pipe','w'),2=>array('pipe','w')),$pipes); 
    $write = array($pipes[0]); 
    $read = array($pipes[1], $pipes[2]); 
    $except = null; 
    $stdout = ''; 
    $stderr = ''; 
    while($r = stream_select($read, $write, $except, null, $timeout)){ 
     foreach($read as $stream){ 

      // handle STDOUT 
      if($stream===$pipes[1]) 
/*...*/   $stdout.=stream_get_contents($stream); 

      // handle STDERR 
      if($stream===$pipes[2]) 
/*...*/   $stderr.=stream_get_contents($stream); 
     } 

     // Handle STDIN (???) 
     if(isset($write[0])) ; 

// the following code is temporary 
$n=isset($n) ? $n+1 : 0; if($n>10)break; // break while loop after 10 iterations 

    } 
} 

La única pieza restante del rompecabezas está manejando STDIN (ver la línea marcada (???)). Me di cuenta de que STDIN debe ser suministrado por lo que sea que llame a mi función, execute(). Pero, ¿y si no quiero usar STDIN en absoluto? En mi caso de prueba, arriba, no pedí comentarios, pero se supone que debo hacer algo con STDIN.

Dicho esto, el enfoque anterior todavía se congela en stream_get_contents(). No estoy muy seguro de qué hacer/probar a continuación.

Créditos

La solución fue sugerido por Jakob Truelsen, así como el descubrimiento de la emisión original. El consejo de 4k también fue su idea. Antes de esto, estaba desconcertado sobre por qué la función funcionaba bien (no sabía que todo dependía del tamaño del búfer).

+0

Si no planea enviar ningún dato, no necesita hacer nada con STDIN. Al igual que '$ except', podría simplemente configurar' $ write' para que sea nulo. – Zoredache

+0

@Zoredache Pero mi problema no es con STDIN, sino STDOUT. Además, esta es la misma razón por la que le dije a Jay (abajo). – Christian

Respuesta

5

Bueno, parece pasó un año y se olvidó de esto todavía está pendiente!

Sin embargo, envolví este desastre en una clase de PHP agradable donde puede encontrar on Github.

El principal problema que queda es que la lectura de STDERR hace que el script PHP para bloquear, por lo que se ha desactivado.

En el lado positivo, gracias a eventos y algo de código agradable (espero!), uno puede interactuar con el proceso que se está ejecutando (de ahí el nombre de clase, InterExec). Entonces puedes tener un comportamiento tipo bot en PHP.

0
while($r = stream_select($read, $write, $except, null, $timeout)){ 

Por lo que yo sé que esto se transformará $ r al número de modificaciones de las corrientes, que puede ser 0 y el bucle ya no continuar. Yo personalmente recodificar esto como se describe en el manual de PHP:

while(false !== ($r = stream_select($read, $write, $except, null, $timeout))){ 

En lo que se refiere a su STDIN si el proceso no es interactivo, entonces el STDIN puede no ser necesario. ¿Cuál es el proceso que está ejecutando?

+0

Esta es una función general. Me gustaría apoyar STDIN por el bien de los usuarios que quieran usar STDIN. :). En cuanto a $ r siendo 0, no me hace mucha diferencia. Todavía se atasca en stream_get_contents(). – Christian

+0

¿ha intentado ver qué va a aceptar? Por ejemplo, un proceso como telnet que podría abrir y luego pasar una 'o' seguida de una nueva línea y luego una dirección xxxx: xx y luego una nueva línea y registrar el STDOUT que obtiene y ver si funciona :) – Jay

+0

Jay - La prueba el código del caso anterior hace exactamente eso, haciendo algunas E/S estándar como cualquier otro programa. – Christian

0

Todo el problema con el bloqueo en stream_get_contents está en la forma en que se crea el proceso. La forma correcta es abrir la salida estándar con el modo de lectura/escritura de la tubería, por ejemplo:

$descriptor = array (0 => array ("pipe", "r"), 1 => array ("pipe", "rw"), 2 => array ("pipe", "rw")); 
//Open the resource to execute $command 
$t->pref = proc_open($command,$descriptor,$t->pipes); 
//Set STDOUT and STDERR to non-blocking 
stream_set_blocking ($t->pipes[0], 0); 
stream_set_blocking ($t->pipes[1], 0); 

Esta es obvio que cuando stream_get_contents quiere leer el tubo de salida estándar que necesita el modo de lectura. El mismo error con colgar/congelar/bloquear es en esta buena clase https://gist.github.com/Arbow/982320

Luego el bloqueo desaparece. Pero leer no lee nada.

3

te has perdido esta nota en el manual de PHP para stream_select():

Cuando stream_select() devuelve, las matrices de leer, escribir y se modifican excepto para indicar qué recurso (s) corriente cambió realmente estado.

Necesita volver a crear las matrices antes de llamar a stream_select() cada vez.

Dependiendo del proceso que está abriendo, esta puede ser la razón por la que su ejemplo aún bloquea.

+0

Lo comprobaré tan pronto como tenga algo de tiempo, pero de hecho es una explicación plausible. ¡Gracias! – Christian

Cuestiones relacionadas