Asynchrone Shell-Ausführung in PHP

199

Ich habe ein PHP-Skript, das ein Shell-Skript aufrufen muss, sich aber überhaupt nicht um die Ausgabe kümmert. Das Shell-Skript führt eine Reihe von SOAP-Aufrufen durch und ist langsam abzuschließen. Daher möchte ich die PHP-Anforderung nicht verlangsamen, während sie auf eine Antwort wartet. Tatsächlich sollte die PHP-Anforderung beendet werden können, ohne den Shell-Prozess zu beenden.

Ich habe in den verschiedenen sah exec(), shell_exec(), pcntl_fork()etc. Funktionen, aber keiner von ihnen scheinen genau zu bieten , was ich will. (Oder wenn doch, ist mir nicht klar, wie.) Irgendwelche Vorschläge?

AdamTheHutt
quelle
Unabhängig davon, für welche Lösung Sie sich entscheiden, sollten Sie auch in Betracht ziehen, das Shell-Skript zu verwenden niceund ionicezu verhindern, dass es Ihr System überfordert (z. B. /usr/bin/ionice -c3 /usr/bin/nice -n19)
rinogo
Mögliches Duplikat von PHP führen einen Hintergrundprozess aus
Sean the Bean

Antworten:

218

Wenn es "die Ausgabe nicht interessiert", konnte der Exec zum Skript nicht mit dem aufgerufen werden & Hintergrund des Prozesses werden?

BEARBEITEN - Wenn Sie berücksichtigen, was @ AdamTheHut zu diesem Beitrag kommentiert hat, können Sie dies einem Aufruf hinzufügen an exec:

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

Dadurch werden sowohl stdio(first >) als auch stderr( 2>) zum /dev/nullHintergrund umgeleitet und ausgeführt.

Es gibt andere Möglichkeiten, dasselbe zu tun, aber dies ist am einfachsten zu lesen.


Eine Alternative zur obigen Doppelumleitung:

" &> /dev/null &"
Labyrinth
quelle
11
Dies scheint zu funktionieren, benötigt aber etwas mehr als ein kaufmännisches Und. Ich habe es zum Laufen gebracht, indem ich "> / dev / null 2> / dev / null &" an den Aufruf von exec () angehängt habe. Obwohl ich zugeben muss, bin ich mir nicht ganz sicher, was das bedeutet.
AdamTheHutt
2
Auf jeden Fall der richtige Weg, wenn Sie Feuer wollen und mit PHP und Apache vergessen. In vielen Produktions-Apache- und PHP-Umgebungen ist pcntl_fork () deaktiviert.
Methode
9
Nur ein Hinweis für &> /dev/null &, xdebug generiert keine Protokolle, wenn Sie dies verwenden. Überprüfen Sie stackoverflow.com/questions/4883171/…
kapeels
5
@MichaelJMulligan schließt die Dateideskriptoren. Trotz der Effizienzgewinne /dev/nullist die Verwendung im Nachhinein die bessere Praxis, da das Schreiben in geschlossene FDs Fehler verursacht, während Versuche zu lesen oder zu schreiben, um /dev/nulleinfach stillschweigend nichts zu tun.
Charles Duffy
3
Hintergrundskripte werden beim Neustart von Apache beendet. Beachten Sie dies nur für sehr lange Jobs oder wenn Sie zeitlich Pech haben und sich fragen, warum Ihr Job verschwunden ist ...
Julien
53

Früher habe ich bei dafür, wie es wirklich ist ein unabhängiger Prozess zu starten.

<?php
    `echo "the command"|at now`;
?>
Czimi
quelle
4
In einigen Situationen ist dies die absolut beste Lösung. Es war das einzige, das für mich funktioniert hat, um einen "sudo reboot" ("echo 'sleep 3; sudo reboot' | at now") von einem Webgui zu veröffentlichen UND das Rendern der Seite zu beenden .. auf openbsd
Kaii
1
Wenn der Benutzer, den Sie Apache ausführen, (normalerweise www-data) nicht über die zu verwendenden Berechtigungen verfügt atund Sie diese nicht konfigurieren können, können Sie versuchen, <?php exec('sudo sh -c "echo \"command\" | at now" ');Wenn commandenthält Anführungszeichen, siehe Escapeeshellarg , um Kopfschmerzen zu vermeiden
Julien
1
Gut darüber nachzudenken, Sudo zum Laufen zu bringen, ist nicht die beste Idee, da es Sudo im Grunde einen Root-Zugriff auf alles gibt. Ich kehrte zurück, um inecho "sudo command" | at nowwww-data/etc/at.deny
Julien
@ Julien Vielleicht könnten Sie mit dem Befehl sudo ein Shell-Skript erstellen und es stattdessen starten. Solange Sie keine vom Benutzer übermittelten Werte an das Skript übergeben
Garet Claborn
26

An alle Windows-Benutzer: Ich habe eine gute Möglichkeit gefunden, ein asynchrones PHP-Skript auszuführen (eigentlich funktioniert es mit fast allem).

Es basiert auf den Befehlen popen () und pclose (). Und funktioniert sowohl unter Windows als auch unter Unix.

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

Originalcode von: http://php.net/manual/en/function.exec.php#86329

LucaM
quelle
Dies führt nur eine Datei aus, funktioniert nicht, wenn Sie PHP / Python / Node usw. verwenden
David Valentino
@davidvalentino Richtig, und das ist in Ordnung! Wenn Sie ein PHP / Pyhton / NodeJS-Skript ausführen möchten, müssen Sie die ausführbare Datei aufrufen und Ihr Skript an sie übergeben. Beispiel: Sie setzen Ihr Terminal nicht ein, myscript.jssondern schreiben node myscript.js. Das heißt: Knoten ist die ausführbare Datei, myscript.js ist das auszuführende Skript. Es gibt einen großen Unterschied zwischen ausführbarer Datei und Skript.
LucaM
Recht , das ist kein Problem in diesem Fall ,, in anderen Fällen Beispiel wie Bedarf Laravel Handwerker laufen, php artisannur hier setzen Kommentar so dass keine Notwendigkeit , warum es nicht funktionieren mit Befehlen Tracing
david valentino
22

Unter Linux können Sie Folgendes tun:

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

Dadurch wird der Befehl an der Eingabeaufforderung ausgeführt und anschließend einfach die PID zurückgegeben, die Sie auf> 0 prüfen können, um sicherzustellen, dass sie funktioniert.

Diese Frage ist ähnlich: Hat PHP Threading?

Darryl Hein
quelle
Diese Antwort wäre leichter zu lesen, wenn Sie nur das Nötigste einbeziehen würden (Eliminierung des action=generate var1_id=23 var2_id=35 gen_id=535Segments). Da OP nach dem Ausführen eines Shell-Skripts gefragt hat, benötigen Sie die PHP-spezifischen Teile nicht. Der endgültige Code wäre:$cmd = 'nohup nice -n 10 /path/to/script.sh > /path/to/log/file.log & printf "%u" $!';
Rinogo
Als Notiz von jemandem, der "schon einmal dort gewesen" ist, könnte jeder, der dies liest, in Betracht ziehen, nicht nur, nicesondern auch zu verwenden ionice.
Rinogo
1
Was bedeutet "% u" $! genau tun?
Zweige
@ Twigs &führt vorhergehenden Code im Hintergrund aus und wird dann printffür die formatierte Ausgabe der $!Variablen verwendet, die die PID enthält
NoChecksum
1
Vielen Dank, ich habe alle möglichen Lösungen ausprobiert, die ich gefunden habe, um sie mit einem asynchronen PHP-Shell-Aufruf in ein Protokoll auszugeben, und Ihre ist die einzige, die alle Kriterien erfüllt.
Josh Powlison
6

Unter Linux können Sie einen Prozess in einem neuen unabhängigen Thread starten, indem Sie am Ende des Befehls ein kaufmännisches Und anhängen

mycommand -someparam somevalue &

In Windows können Sie den DOS-Befehl "start" verwenden

start mycommand -someparam somevalue
Löwe
quelle
2
Unter Linux kann das übergeordnete Element weiterhin blockieren, bis das untergeordnete Element die Ausführung beendet hat, wenn es versucht, aus einem offenen Dateihandle zu lesen, das im Unterprozess enthalten ist (dh stdout). Dies ist also keine vollständige Lösung.
Charles Duffy
1
Getesteter startBefehl unter Windows, er wird nicht asynchron ausgeführt ... Können Sie die Quelle angeben, von der Sie diese Informationen erhalten haben?
Alph.Dev
@ Alph.Dev Bitte werfen Sie einen Blick auf meine Antwort, wenn Sie Windows verwenden: stackoverflow.com/a/40243588/1412157
LucaM
@mynameis Ihre Antwort zeigt genau, warum der Startbefehl NICHT funktioniert hat. Es liegt am /BParameter. Ich habe es hier erklärt: stackoverflow.com/a/34612967/1709903
Alph.Dev
6

Der richtige Weg (!) dazu ist

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

Fork Forks, Setsid weisen den aktuellen Prozess an, ein Master-Prozess zu werden (kein übergeordnetes Element). Execve weist den aufrufenden Prozess an, durch den aufgerufenen Prozess ersetzt zu werden. damit der Elternteil kündigen kann, ohne das Kind zu beeinträchtigen.

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

 // parent's stuff
 exit();

quelle
6
Das Problem mit pcntl_fork () ist, dass Sie es nicht verwenden sollten, wenn Sie unter einem Webserver ausgeführt werden, wie es das OP tut (außerdem hat das OP dies bereits versucht).
Guss
6

Ich habe das benutzt ...

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

(wo PHP_PATHist eine Konstante wie define('PHP_PATH', '/opt/bin/php5')oder ähnlich definiert )

Es werden Argumente über die Befehlszeile übergeben. Informationen zum Lesen in PHP finden Sie unter argv .

Philfreo
quelle
4

Der einzige Weg, den ich fand, der wirklich für mich funktionierte, war:

shell_exec('./myscript.php | at now & disown')
Gordon Forsythe
quelle
3
'disown' ist ein integrierter Bash und funktioniert auf diese Weise nicht mit shell_exec (). Ich habe es versucht shell_exec("/usr/local/sbin/command.sh 2>&1 >/dev/null | at now & disown")und alles was ich bekomme ist:sh: 1: disown: not found
Thomas Daugaard
4

Ich fand auch Symfony Process Component nützlich dafür.

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

Sehen Sie, wie es in seinem GitHub-Repo funktioniert .

Anton Pelykh
quelle
2
Warnung: Wenn Sie nicht warten, wird der Prozess beendet, wenn die Anforderung endet
the_nuts
Perfektes Werkzeug, das auf proc_*internen Funktionen basiert .
MAChitgarha
2

Sie können das PHP-Skript auch als Daemon oder Cronjob ausführen :#!/usr/bin/php -q

Ronald Conco
quelle
1

Verwenden Sie ein benanntes Fifo.

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

Wenn Sie dann die lange laufende Aufgabe starten möchten, schreiben Sie einfach eine neue Zeile (nicht blockierend in die Triggerdatei).

Solange Ihre Eingabe kleiner als PIPE_BUFund eine einzelne write()Operation ist, können Sie Argumente in das FIFO schreiben und sie wie $REPLYim Skript anzeigen lassen .

Geocar
quelle
1

Ohne Verwendungswarteschlange können Sie Folgendes verwenden proc_open():

    $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);
Kris Roofe
quelle