2008-10-21 12 views
174

Tengo un script PHP que necesita invocar un script de shell pero no le importa en absoluto el resultado. El script de shell realiza varias llamadas SOAP y tarda en completarse, por lo que no quiero ralentizar la solicitud de PHP mientras espera una respuesta. De hecho, la solicitud de PHP debería poder salir sin finalizar el proceso de shell.Ejecutor de shell asincrónico en PHP

He examinado varias funciones de exec(), shell_exec(), pcntl_fork(), etc., pero ninguna parece ofrecer exactamente lo que quiero. (O, si lo hacen, no me queda claro cómo). ¿Alguna sugerencia?

+0

No importa qué solución elija, también debería considerar usar 'nice' y' ionice' para evitar que la secuencia de comandos de shell abrume su sistema (por ejemplo, '/ usr/bin/ionice -c3/usr/bin/nice -n19 ') – rinogo

+0

Posible duplicado de [php ejecutar un proceso en segundo plano] (https://stackoverflow.com/questions/45953/php-execute-a-background-process) –

Respuesta

188

Si "no le importa la salida", ¿no se pudo llamar al ejecutor al script con el & para crear un fondo del proceso?

EDITAR - incorporando lo que @AdamTheHut comentado a este post, se puede agregar esto a una llamada a exec:

" > /dev/null 2>/dev/null &" 

que redirigirá tanto stdio (primera >) y stderr (2>) a /dev/null y se ejecuta en segundo plano.

Hay otras maneras de hacer lo mismo, pero esta es la más simple de leer.


Una alternativa a la doble redirección anterior:

" &> /dev/null &" 
+8

Esto parece funcionar, pero necesita un poco más que un ampersand. Lo hice funcionar agregando ">/dev/null 2>/dev/null &" a la llamada de exec(). Aunque tengo que admitir que no estoy exactamente seguro de lo que hace eso. – AdamTheHutt

+1

que redirige stdio a/dev/null y stderr a/dev/null ..buena captura en la adición a la llamada – warren

+2

Definitivamente el camino a seguir si quieres fuego y olvídate con php y apache. Una gran cantidad de entornos Apache y PHP de producción tendrán pcntl_fork() deshabilitado. – method

51

Solía ​​en para esto, ya que está empezando un proceso independiente.

<?php 
    `echo "the command"|at now`; 
?> 
+3

en algunas situaciones esto es abs absolutamente la mejor solución. fue el único que funcionó para mí para lanzar un "reinicio sudo" ("echo 'sleep 3; sudo reiniciar' | at now") desde un webgui Y terminar de renderizar la página ... en openbsd – Kaii

+0

si el usuario ejecuta Apache (por lo general 'www-data') no tiene los permisos para usar' at' y usted no puede configurarlo, puede intentar usar ' Julien

+1

Bueno, pensándolo bien, obtener sudo para ejecutar sh no es la mejor idea, ya que básicamente le da a sudo un acceso de root a todo. Volví a usar el comando sudo 'echo' "| en este momento 'y comenta' www-data' en '/ etc/at.deny' – Julien

18

En Linux puede hacer lo siguiente:

$cmd = 'nohup nice -n 10 php -f php/file.php > log/file.log & printf "%u" $!'; 
$pid = shell_exec($cmd); 

Esto ejecutará el comando en el comando puntualmente y luego simplemente devolver el PID, que se puede comprobar si hay> 0 para asegurarse de que funcionaba.

Esta pregunta es similar: Does PHP have threading?

+0

Esta respuesta sería más fácil de leer si incluyese solo los elementos esenciales (eliminando' action = generate var1_id = 23 var2_id = 35 gen_id = 535' segmento). Además, dado que OP preguntó acerca de ejecutar un script de shell, no necesita las porciones específicas de PHP. El código final sería: '$ cmd = 'nohup nice -n 10 /path/to/script.sh> /path/to/log/file.log & printf"% u "$!';' – rinogo

+0

Además, como una nota de alguien que ha "estado allí antes", cualquiera que lea esto podría considerar usar no solo 'nice' sino' ionice'. – rinogo

+1

¿Qué significa "% u" $! hacer exactamente? – Twigs

6

En Linux, puede iniciar un proceso en un nuevo subproceso independiente añadiendo un símbolo de unión al final del comando

mycommand -someparam somevalue & 

En Windows , puede utilizar el comando "start" DOS

start mycommand -someparam somevalue 
+2

En Linux, el padre aún puede bloquear hasta que el hijo haya terminado de ejecutarse si está tratando de leer desde un archivo abierto manejado por el subproceso (es decir, stdout), por lo que esta no es una solución completa. –

+1

Probado el comando 'start' en Windows, no se ejecuta de forma asíncrona ... ¿Podría incluir el origen de donde obtuvo esa información? –

+0

@ Alph.Dev, por favor, eche un vistazo a mi respuesta si está usando Windows: http://stackoverflow.com/a/40243588/1412157 – LucaM

1

también puede ejecutar el script PHP como demonio o tarea programada: #!/usr/bin/php -q

5

de la manera correcta (!) Para hacerlo es

  1. tenedor()
  2. setsid()
  3. execve()

tenedor horquillas, setsid decirle al actual proceso de convertirse en un maestro (ninguno de los padres) , indique que el proceso de llamada sea reemplazado por el llamado. para que el padre pueda dejar de fumar sin afectar al niño.

$pid=pcntl_fork(); 
if($pid==0) 
{ 
    posix_setsid(); 
    pcntl_exec($cmd,$args,$_ENV); 
    // child becomes the standalone detached process 
} 

// parent's stuff 
exit(); 
+6

El problema con pcntl_fork() es que no debe usarlo cuando se ejecuta bajo un servidor web, como lo hace el OP (además, el OP ya lo ha intentado). – Guss

1

Usa un nombre fifo.

#!/bin/sh 
mkfifo trigger 
while true; do 
    read < trigger 
    long_running_task 
done 

Entonces cada vez que desee iniciar la tarea de larga duración, sólo tiene que escribir una nueva línea (sin bloqueo en el fichero de gatillo.

Siempre y cuando su entrada es menor que PIPE_BUF y es una sola operación write(), se puede escribir argumentos en la FIFO y hacer que se muestran como $REPLY en el guión.

4

utilicé esta ...

/** 
* Asynchronously execute/include a PHP file. Does not record the output of the file anywhere. 
* Relies on the PHP_PATH config constant. 
* 
* @param string $filename file to execute 
* @param string $options (optional) arguments to pass to file via the command line 
*/ 
function asyncInclude($filename, $options = '') { 
    exec(PHP_PATH . " -f {$filename} {$options} >> /dev/null &"); 
} 

(donde PHP_PATH es un const definido como define('PHP_PATH', '/opt/bin/php5') o similar)

Pasa los argumentos a través de la línea de comando. Para leerlos en PHP, vea argv.

3

La única forma que he encontrado que realmente funcionó para mí fue:

shell_exec('./myscript.php | at now & disown') 
+3

'repudiar' es un Bash incorporado y no funciona con shell_exec() de esta manera. Intenté 'shell_exec ("/usr/local/sbin/command.sh 2> & 1>/dev/null | at now & disown ")' y todo lo que obtengo es: 'sh: 1: renegado: no encontrado' –

14

Para todos los usuarios de Windows: He encontrado una buena manera de ejecutar un script PHP asíncrona (en realidad trabaja con casi todo).

Se basa en los comandos popen() y pclose(). Y funciona bien tanto en Windows como en Unix.

function execInBackground($cmd) { 
    if (substr(php_uname(), 0, 7) == "Windows"){ 
     pclose(popen("start /B ". $cmd, "r")); 
    } 
    else { 
     exec($cmd . " > /dev/null &"); 
    } 
} 

Código original de: http://php.net/manual/en/function.exec.php#86329

+0

Worked ¡muy bien para mí! –

0

sin el uso de colas, se puede utilizar el proc_open() así:

$descriptorspec = array(
     0 => array("pipe", "r"), 
     1 => array("pipe", "w"), 
     2 => array("pipe", "w") //here curaengine log all the info into stderror 
    ); 
    $command = 'ping stackoverflow.com'; 
    $process = proc_open($command, $descriptorspec, $pipes); 
2

También encontré Symfony Process Component útil para esto.

use Symfony\Component\Process\Process; 

$process = new Process('ls -lsa'); 
// ... run process in background 
$process->start(); 

// ... do other things 

// ... if you need to wait 
$process->wait(); 

// ... do things after the process has finished 

Mira cómo funciona en su GitHub repo.