Shell asincrono exec in PHP


199

Ho uno script PHP che deve invocare uno script shell ma non si preoccupa affatto dell'output. Lo script della shell effettua un numero di chiamate SOAP ed è lento da completare, quindi non voglio rallentare la richiesta PHP mentre attende una risposta. In effetti, la richiesta PHP dovrebbe essere in grado di uscire senza terminare il processo di shell.

Ho guardato nelle varie exec(), shell_exec(), pcntl_fork()funzioni, ecc, ma nessuno di loro sembrano offrire esattamente quello che voglio. (O, se lo fanno, non mi è chiaro come.) Qualche suggerimento?


Indipendentemente dalla soluzione scelta, dovresti anche considerare di utilizzare nicee ioniceimpedire allo script della shell di sovraccaricare il tuo sistema (ad es. /usr/bin/ionice -c3 /usr/bin/nice -n19)
rinogo

Risposte:


218

Se "non si preoccupa dell'output", non è possibile richiamare il exec con lo script con lo &sfondo del processo?

EDIT : incorporando ciò che @ AdamTheHut ha commentato a questo post, puoi aggiungerlo a una chiamata a exec:

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

Ciò reindirizzerà stdio(first >) e stderr( 2>) a /dev/nulled eseguirà in background.

Esistono altri modi per fare la stessa cosa, ma questa è la più semplice da leggere.


Un'alternativa al doppio reindirizzamento sopra:

" &> /dev/null &"

11
Questo sembra funzionare, ma ha bisogno di poco più di una e commerciale. Ho funzionato aggiungendo "> / dev / null 2> / dev / null &" alla chiamata exec (). Anche se devo ammettere che non sono esattamente sicuro di cosa faccia.
AdamTheHutt,

2
Sicuramente la strada da percorrere se vuoi il fuoco e dimenticare con php e apache. Molti ambienti di produzione Apache e PHP avranno pcntl_fork () disabilitato.
metodo

9
Solo una nota per &> /dev/null &, xdebug non genererà registri se lo usi. Controllare stackoverflow.com/questions/4883171/...
kapeels

5
@MichaelJMulligan chiude i descrittori di file. Detto questo, nonostante il miglioramento dell'efficienza, a ben vedere, l'uso /dev/nullè la migliore pratica, poiché la scrittura su FD chiusi provoca errori, mentre i tentativi di leggere o scrivere /dev/nullsemplicemente non fanno nulla in silenzio.
Charles Duffy,

3
Gli script in background verranno uccisi al riavvio di Apache ... basta essere consapevoli di questo per lavori molto lunghi o se sei sfortunato in termini di tempismo e ti chiedi perché il tuo lavoro è scomparso ...
Julien

53

Ho usato a questo, in quanto è davvero iniziando un processo indipendente.

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

4
in alcune situazioni questa è assolutamente la soluzione migliore. è stato l'unico che ha funzionato per me per rilasciare un "sudo reboot" ("echo 'sleep 3; sudo reboot' | at now") da un webgui E finire il rendering della pagina .. su openbsd
Kaii

1
se l'utente con cui apache apache (di solito www-data) non dispone delle autorizzazioni da utilizzare ate non è possibile configurarlo, puoi provare a utilizzare <?php exec('sudo sh -c "echo \"command\" | at now" ');Se commandcontiene virgolette, vedi escapeshellarg per risparmiare mal di testa
Julien

1
Pensandoci bene, far funzionare sh sudo non è la migliore idea, dato che in pratica dà a sudo un accesso root a tutto. Sono tornato a utilizzare echo "sudo command" | at nowe commentare www-datain/etc/at.deny
Julien

@Julien forse potresti creare uno script di shell con il comando sudo e lanciarlo invece. fintanto che non stai trasmettendo alcun valore da parte dell'utente a detto script
Garet Claborn,

26

Per tutti gli utenti Windows: ho trovato un buon modo per eseguire uno script PHP asincrono (in realtà funziona con quasi tutto).

È basato sui comandi popen () e pclose (). E funziona bene su Windows e Unix.

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

Codice originale da: http://php.net/manual/en/function.exec.php#86329


questo esegue solo un file, non funziona se si utilizza php / python / node ecc
david valentino

@davidvalentino Corretto, e va bene! Se desideri eseguire uno script PHP / Pyhton / NodeJS devi effettivamente chiamare l'eseguibile e passarci lo script. Ad esempio: non inserisci il tuo terminale myscript.jsma invece scriverai node myscript.js. Cioè: node è l'eseguibile, myscript.js è, beh, lo script da eseguire. C'è un'enorme differenza tra eseguibile e script.
Luca,

giusto che in questo caso non c'è problema, in altri casi come l'esigenza di eseguire laravel artisan, php artisanbasta inserire un commento qui quindi non c'è bisogno di tracciare perché non funzionerà con i comandi
David Valentino

22

Su Linux puoi fare quanto segue:

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

Questo eseguirà il comando al prompt dei comandi e quindi restituirà il PID, che è possibile controllare per> 0 per assicurarsi che abbia funzionato.

Questa domanda è simile: PHP ha il threading?


Questa risposta sarebbe più facile da leggere se includessi solo gli elementi essenziali essenziali (eliminando il action=generate var1_id=23 var2_id=35 gen_id=535segmento). Inoltre, poiché OP ha chiesto di eseguire uno script di shell, non sono necessarie le parti specifiche di PHP. Il codice finale sarebbe:$cmd = 'nohup nice -n 10 /path/to/script.sh > /path/to/log/file.log & printf "%u" $!';
rinogo

Inoltre, come nota di chi è stato "già presente", chiunque legga questo potrebbe prendere in considerazione l'utilizzo non solo nicema anche ionice.
Rinogo,

1
Cosa significa "% u" $! fare esattamente?
Ramoscelli

@Twigs &esegue il codice precedente in background, quindi printfviene utilizzato per l'output formattato della $!variabile che contiene il PID
NoChecksum

1
Grazie mille, ho provato tutti i tipi di soluzioni che stavo trovando per l'output in un registro con una chiamata shell PHP asincrona e la tua è l'unica che ha soddisfatto tutti i criteri.
Josh Powlison,


6

In Linux, è possibile avviare un processo in un nuovo thread indipendente aggiungendo una e commerciale alla fine del comando

mycommand -someparam somevalue &

In Windows, è possibile utilizzare il comando DOS "start"

start mycommand -someparam somevalue

2
Su Linux, il genitore può ancora bloccare fino a quando il figlio non ha terminato l'esecuzione se sta tentando di leggere da un handle di file aperto gestito dal sottoprocesso (es. Stdout), quindi questa non è una soluzione completa.
Charles Duffy,

1
startComando testato su Windows, non viene eseguito in modo asincrono ... Potresti includere la fonte da cui hai ottenuto tali informazioni?
Alph.Dev,

@ Alph.Dev prega di dare un'occhiata alla mia risposta, se si utilizza Windows: stackoverflow.com/a/40243588/1412157
Lucam

@mynameis La tua risposta mostra esattamente perché il comando di avvio NON funzionava. È a causa del /Bparametro. Ho spiegato qui: stackoverflow.com/a/34612967/1709903
Alph.Dev

6

il modo giusto (!) per farlo è farlo

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

fork forks, setsid indica al processo corrente di diventare master (no parent), esegui indica al processo chiamante di essere sostituito da quello chiamato. in modo che il genitore possa smettere senza influire sul figlio.

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

 // parent's stuff
 exit();

6
Il problema con pcntl_fork () è che non dovresti usarlo durante l'esecuzione su un server web, come fa l'OP (inoltre l'OP ha già provato questo).
Guss,

6

Ho usato questo ...

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

(dove PHP_PATHè una const definita come define('PHP_PATH', '/opt/bin/php5')o simile)

Passa in argomenti tramite la riga di comando. Per leggerli in PHP, vedi argv .


4

L'unico modo in cui ho scoperto che ha funzionato davvero per me è stato:

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

3
'disown' è un Bash integrato e non funziona con shell_exec () in questo modo. Ho provato shell_exec("/usr/local/sbin/command.sh 2>&1 >/dev/null | at now & disown")e tutto quello che ottengo è:sh: 1: disown: not found
Thomas Daugaard,

4

Ho anche trovato utile Symfony Process Component per questo.

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

Guarda come funziona nel suo repository GitHub .


2
Avvertenza: se non aspetti, il processo verrà
interrotto al

Strumento perfetto, basato su proc_*funzioni interne.
MAChitgarha,

2

Puoi anche eseguire lo script PHP come demone o cronjob :#!/usr/bin/php -q


1

Usa un FIFO chiamato.

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

Quindi, ogni volta che si desidera avviare l'attività con esecuzione prolungata, è sufficiente scrivere una nuova riga (senza blocco nel file di trigger.

Finché il tuo input è più piccolo di PIPE_BUFed è una singola write()operazione, puoi scrivere argomenti nel Fifo e farli apparire come $REPLYnello script.


1

senza utilizzare la coda, è possibile utilizzare in proc_open()questo modo:

    $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);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.