2008-10-21 9 views
174

J'ai un script PHP qui doit invoquer un script shell mais qui ne se soucie pas du tout de la sortie. Le script shell fait un certain nombre d'appels SOAP et est lent à terminer, donc je ne veux pas ralentir la requête PHP pendant qu'il attend une réponse. En fait, la requête PHP devrait pouvoir se terminer sans terminer le processus shell.Exécution de shell asynchrone en PHP

J'ai examiné les diverses fonctions exec(), shell_exec(), pcntl_fork(), etc., mais aucune d'elles ne semble offrir exactement ce que je veux. (Ou, s'ils le font, ce n'est pas clair pour moi comment.) Des suggestions?

+0

Quelle que soit la solution choisie, vous devriez aussi utiliser 'nice' et' ionice' pour éviter que le script shell ne submerge votre système (par exemple '/ usr/bin/ionice -c3/usr/bin/nice -n19 ') – rinogo

+0

Copie possible de [php exécuter un processus d'arrière-plan] (https://stackoverflow.com/questions/45953/php-execute-a-background-process) –

Répondre

188

Si "ne se soucie pas de la sortie", ne peut-on pas appeler l'exec au script avec le & pour mettre en arrière-plan le processus?

EDIT - incorporant ce que @AdamTheHut a commenté à ce poste, vous pouvez ajouter à un appel à exec:

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

qui redirigera les deux stdio (première >) et stderr (2>) à /dev/null et courir en arrière-plan.

Il existe d'autres façons de faire la même chose, mais c'est la plus simple à lire.


Une alternative à la double-redirect ci-dessus:

" &> /dev/null &" 
+8

Cela semble fonctionner, mais il faut un peu plus d'un esperluette. Je l'ai fait travailler en ajoutant ">/dev/null 2>/dev/null &" à l'appel de exec(). Bien que je dois admettre que je ne suis pas exactement sûr de ce que cela fait. – AdamTheHutt

+1

qui redirige stdio vers/dev/null et stderr vers/dev/null ..bonne prise sur l'addition à l'appel – warren

+2

Certainement le chemin à parcourir si vous voulez le feu et oublier avec PHP et Apache. Un grand nombre d'environnements de production Apache et PHP auront pcntl_fork() désactivé. – method

51

je à pour cela, car il commence vraiment à un processus indépendant.

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

dans certaines situations c'est abs olutely la meilleure solution. c'est le seul qui a fonctionné pour moi de sortir un "sudo reboot" ("echo 'sleep 3; sudo reboot' à présent") à partir d'un webgui ET finir de rendre la page .. sur openbsd – Kaii

+0

si l'utilisateur exécute apache (habituellement 'www-data') n'a pas les permissions pour utiliser' at' et vous ne pouvez pas le configurer, vous pouvez essayer d'utiliser ' Julien

+1

Bien pensé, Obtenir sudo pour exécuter sh n'est pas la meilleure idée, car cela donne à sudo un accès root à tout. Je suis revenu à utiliser 'echo" sudo commande "| à présent »et commentant' www-data' out '/ etc/at.deny' – Julien

18

Sur Linux, vous pouvez effectuer les opérations suivantes:

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

Cela exécutera la commande à la commande prompty puis il suffit de retourner le PID, que vous pouvez vérifier> 0 pour assurer cela a fonctionné.

Cette question est similaire: Does PHP have threading?

+0

Cette réponse serait plus facile à lire si vous n'incluiez que l'essentiel (en éliminant le' action = générer var1_id = 23 var2_id = 35 gen_id = Segment 535'). En outre, puisque OP a demandé l'exécution d'un script shell, vous n'avez pas besoin des portions spécifiques à PHP. Le code final serait: '$ cmd = 'nohup nice -n 10 /path/to/script.sh> /path/to/log/file.log & printf"% u "$!';' – rinogo

+0

Aussi, comme une note de quelqu'un qui a "été là avant", toute personne lisant cela pourrait envisager d'utiliser non seulement "nice" mais aussi "ionice". – rinogo

+1

Qu'est-ce que "% u" $! faire exactement? – Twigs

6

Dans Linux, vous pouvez commencer un processus dans un nouveau thread indépendant en ajoutant une esperluette à la fin de la commande

mycommand -someparam somevalue & 

Dans Windows , vous pouvez utiliser la commande DOS "start"

start mycommand -someparam somevalue 
+2

Sous Linux, le parent peut toujours bloquer jusqu'à ce que l'enfant ait fini de s'exécuter s'il essaie de lire depuis un descripteur de fichier ouvert détenu par le sous-processus (stdout), ce n'est donc pas une solution complète. –

+1

Testé la commande 'start' sur Windows, il ne s'exécute pas de manière asynchrone ... Pourriez-vous inclure la source d'où vous avez obtenu cette information? –

+0

@ Alph.Dev s'il vous plaît jeter un oeil à ma réponse si vous utilisez Windows: http://stackoverflow.com/a/40243588/1412157 – LucaM

1

vous pouvez également exécuter le script PHP comme démon ou cronjob: #!/usr/bin/php -q

5

le bon chemin (!) Pour le faire est de

  1. fork()
  2. setsid()
  3. exec()

des fourches de la fourche, setsid dire le processus en cours de devenir un maître (pas de parent) , execve dit que le processus appelant doit être remplacé par celui appelé. afin que le parent puisse quitter sans affecter l'enfant.

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

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

Le problème avec pcntl_fork() est que vous n'êtes pas censé l'utiliser lors de l'exécution sous un serveur web, comme le fait l'OP (en outre, l'OP l'a déjà essayé). – Guss

1

Utilisez un fifo nommé.

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

Ensuite, chaque fois que vous voulez commencer la longue tâche en cours d'exécution, il suffit d'écrire une nouvelle ligne (non bloquante au fichier déclencheur.

Tant que votre entrée est plus petite que PIPE_BUF et il est une opération write() unique, vous peut écrire des arguments dans le PEPS et les faire apparaître comme $REPLY dans le script.

4

je ... ce

/** 
* 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 &"); 
} 

(où PHP_PATH est un const défini comme define('PHP_PATH', '/opt/bin/php5') ou similaire)

Il passe les arguments via la ligne de commande. Pour les lire en PHP, voir argv.

3

La seule façon que je trouve que vraiment travaillé pour moi était:

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

'disown' est un Bash intégré et ne fonctionne pas avec shell_exec() de cette façon. J'ai essayé 'shell_exec ("/usr/local/sbin/command.sh 2> & 1>/dev/null | à ​​maintenant & disown ")' et 'tout ce que je reçois est:' sh: 1: disown: non trouvé' –

14

à tous les utilisateurs de Windows: J'ai trouvé un bon moyen d'exécuter un script PHP asynchrone (en fait il fonctionne avec presque tout).

Il est basé sur les commandes popen() et pclose(). Et fonctionne bien à la fois sur Windows et Unix.

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

Code original de: http://php.net/manual/en/function.exec.php#86329

+0

très bien pour moi! –

0

sans file d'attente d'utilisation, vous pouvez utiliser le proc_open() comme ceci:

$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

J'ai aussi trouvé Symfony Process Component utile pour cela.

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 

Voir comment cela fonctionne dans GitHub repo.

Questions connexes