Asynchronous shell exec di PHP

199

Saya punya skrip PHP yang perlu menjalankan skrip shell tetapi tidak peduli sama sekali tentang hasilnya. Script shell membuat beberapa panggilan SOAP dan lambat untuk diselesaikan, jadi saya tidak ingin memperlambat permintaan PHP sementara menunggu balasan. Faktanya, permintaan PHP harus bisa keluar tanpa menghentikan proses shell.

Saya telah melihat ke berbagai exec(), shell_exec(), pcntl_fork(), dll fungsi, namun tidak satupun dari mereka tampaknya menawarkan apa yang saya inginkan. (Atau, jika mereka melakukannya, tidak jelas bagiku caranya.) Ada saran?

AdamTheHutt
sumber
Apa pun solusi yang Anda pilih, Anda juga harus mempertimbangkan untuk menggunakan nicedan ionicemencegah skrip shell membanjiri sistem Anda (mis. /usr/bin/ionice -c3 /usr/bin/nice -n19)
rinogo
Kemungkinan duplikat dari php menjalankan proses latar belakang
Sean the Bean

Jawaban:

218

Jika "tidak peduli dengan output", bisakah eksekutif skrip dipanggil dengan &latar belakang proses?

EDIT - memasukkan apa yang dikomentari @ AdamTheHut ke pos ini, Anda dapat menambahkan ini ke panggilan ke exec:

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

Itu akan mengarahkan ulang stdio(pertama >) dan stderr( 2>) ke /dev/nulldan berjalan di latar belakang.

Ada cara lain untuk melakukan hal yang sama, tetapi ini adalah yang paling sederhana untuk dibaca.


Alternatif untuk pengalihan ganda di atas:

" &> /dev/null &"
warren
sumber
11
Ini tampaknya berhasil, tetapi perlu sedikit lebih dari ampersand. Saya membuatnya bekerja dengan menambahkan "> / dev / null 2> / dev / null &" ke panggilan exec (). Meskipun saya harus mengakui, saya tidak yakin apa yang dilakukannya.
AdamTheHutt
2
Pasti cara untuk pergi jika Anda ingin api dan lupakan dengan php dan apache. Banyak lingkungan produksi Apache dan PHP akan menonaktifkan pcntl_fork ().
metode
9
Hanya catatan untuk &> /dev/null &, xdebug tidak akan menghasilkan log jika Anda menggunakan ini. Periksa stackoverflow.com/questions/4883171/…
kapeels
5
@MichaelJMulligan menutup deskriptor file. Meskipun demikian, terlepas dari peningkatan efisiensi, di belakang, penggunaan /dev/nulladalah praktik yang lebih baik, karena menulis ke FD yang ditutup menyebabkan kesalahan, sedangkan upaya membaca atau menulis /dev/nullhanya diam-diam tidak melakukan apa-apa.
Charles Duffy
3
Skrip latar belakang akan dimatikan ketika memulai kembali apache ... hanya sadari ini untuk pekerjaan yang sangat lama atau jika Anda kurang beruntung karena waktu dan Anda bertanya-tanya mengapa pekerjaan Anda menghilang ...
Julien
53

Saya menggunakan di untuk ini, karena benar-benar mulai proses yang independen.

<?php
    `echo "the command"|at now`;
?>
Czimi
sumber
4
dalam beberapa situasi ini benar-benar solusi terbaik. itu adalah satu-satunya yang bekerja bagi saya untuk merilis "sudo reboot" ("echo 'sleep 3; sudo reboot' | at now") dari webgui DAN selesai merender halaman .. di openbsd
Kaii
1
jika pengguna yang Anda jalankan apache (biasanya www-data) tidak memiliki izin untuk menggunakan atdan Anda tidak dapat mengonfigurasinya, Anda dapat mencoba menggunakan <?php exec('sudo sh -c "echo \"command\" | at now" ');Jika commandberisi kutipan, lihat escapeshellarg untuk menghemat sakit kepala Anda
Julien
1
Memikirkannya dengan baik, menjalankan sudo bukanlah ide yang terbaik, karena pada dasarnya memberikan sudo akses root ke semuanya. Saya kembali menggunakan echo "sudo command" | at nowdan berkomentar www-datadi/etc/at.deny
Julien
@ Julien mungkin Anda bisa membuat skrip shell dengan perintah sudo dan meluncurkannya. selama Anda tidak meneruskan nilai yang dikirimkan pengguna ke skrip tersebut
Garet Claborn
26

Untuk semua pengguna Windows: Saya menemukan cara yang baik untuk menjalankan skrip PHP asinkron (sebenarnya berfungsi dengan hampir semua).

Ini didasarkan pada perintah popen () dan pclose (). Dan berfungsi dengan baik pada Windows dan Unix.

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

Kode asli dari: http://php.net/manual/en/function.exec.php#86329

LucaM
sumber
ini hanya menjalankan file, tidak berfungsi jika menggunakan php / python / node dll
david valentino
@davidvalentino Benar, dan itu bagus! Jika Anda ingin mengeksekusi skrip PHP / Pyhton / NodeJS, Anda harus benar-benar memanggil executable dan meneruskan skrip Anda. Misalnya: Anda tidak memasukkan terminal Anda myscript.jstetapi sebaliknya, Anda akan menulis node myscript.js. Yaitu: node adalah executable, myscript.js adalah script yang harus dieksekusi. Ada perbedaan besar antara executable dan skrip.
LucaM
benar itu tidak masalah dalam kasus itu ,, dalam kasus lain contoh seperti perlu menjalankan pengrajin laravel, php artisancukup beri komentar di sini sehingga tidak perlu melacak mengapa itu tidak akan bekerja dengan perintah
david valentino
22

Di linux Anda dapat melakukan hal berikut:

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

Ini akan mengeksekusi perintah pada prompt perintah dan kemudian hanya mengembalikan PID, yang Anda dapat memeriksa> 0 untuk memastikan itu berfungsi.

Pertanyaan ini serupa: Apakah PHP memiliki threading?

Darryl Hein
sumber
Jawaban ini akan lebih mudah dibaca jika Anda hanya memasukkan hal-hal mendasar (menghilangkan action=generate var1_id=23 var2_id=35 gen_id=535segmen). Juga, karena OP bertanya tentang menjalankan skrip shell, Anda tidak memerlukan bagian khusus PHP. Kode terakhirnya adalah:$cmd = 'nohup nice -n 10 /path/to/script.sh > /path/to/log/file.log & printf "%u" $!';
rinogo
Juga, sebagai catatan dari orang yang "pernah ke sana", siapa pun yang membaca ini mungkin mempertimbangkan untuk menggunakan tidak hanya nicetetapi juga ionice.
rinogo
1
Apa artinya "% u" $! lakukan persis?
Ranting
@Twigs &menjalankan kode sebelumnya di latar belakang, kemudian printfdigunakan untuk output terformat dari $!variabel yang berisi PID
NoChecksum
1
Terima kasih banyak, saya mencoba segala macam solusi yang saya temukan untuk output ke log dengan panggilan shell PHP async dan Anda adalah satu-satunya yang memenuhi semua kriteria.
Josh Powlison
6

Di Linux, Anda dapat memulai proses di utas independen baru dengan menambahkan tanda dan di akhir perintah

mycommand -someparam somevalue &

Di Windows, Anda dapat menggunakan perintah DOS "mulai"

start mycommand -someparam somevalue
Leo
sumber
2
Di Linux, orangtua masih dapat memblokir sampai anak selesai berjalan jika mencoba membaca dari pegangan file terbuka yang dipegang oleh subproses (mis. Stdout), jadi ini bukan solusi lengkap.
Charles Duffy
1
Diuji startperintah di windows, itu tidak berjalan secara tidak sinkron ... Bisakah Anda memasukkan sumber dari mana Anda mendapatkan informasi itu?
Alph.Dev
@ Alph.Dev silakan lihat jawaban saya jika Anda menggunakan Windows: stackoverflow.com/a/40243588/1412157
LucaM
@mameameis Jawaban Anda menunjukkan dengan tepat mengapa perintah mulai TIDAK berfungsi. Itu karena /Bparameternya. Saya sudah menjelaskannya di sini: stackoverflow.com/a/34612967/1709903
Alph.Dev
6

cara yang tepat (!) untuk melakukannya adalah dengan

  1. garpu()
  2. setsid ()
  3. mengeksekusi ()

fork fork, setsid memberi tahu proses saat ini untuk menjadi master satu (tidak ada orangtua), mengeksekusi memberitahu proses panggilan untuk digantikan oleh yang dipanggil. sehingga orang tua dapat berhenti tanpa mempengaruhi anak.

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

 // parent's stuff
 exit();

sumber
6
Masalah dengan pcntl_fork () adalah bahwa Anda tidak seharusnya menggunakannya saat berjalan di bawah server web, seperti yang dilakukan OP (selain itu, OP sudah mencoba ini).
Guss
6

Saya menggunakan ini ...

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

(Dimana PHP_PATHconst didefinisikan suka define('PHP_PATH', '/opt/bin/php5')atau serupa)

Itu lewat dalam argumen melalui baris perintah. Untuk membacanya dalam PHP, lihat argv .

philfreo
sumber
4

Satu-satunya cara saya menemukan yang benar-benar bekerja untuk saya adalah:

shell_exec('./myscript.php | at now & disown')
Gordon Forsythe
sumber
3
'disown' adalah Bash bawaan dan tidak bekerja dengan shell_exec () dengan cara ini. Saya mencoba shell_exec("/usr/local/sbin/command.sh 2>&1 >/dev/null | at now & disown")dan yang saya dapatkan adalah:sh: 1: disown: not found
Thomas Daugaard
4

Saya juga menemukan Komponen Proses Symfony berguna untuk ini.

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

Lihat cara kerjanya di repo GitHub -nya .

Anton Pelykh
sumber
2
Peringatan: jika Anda tidak menunggu, prosesnya akan dimatikan ketika permintaan berakhir
the_nuts
Alat yang sempurna, yang didasarkan pada proc_*fungsi internal.
MAChitgarha
2

Anda juga dapat menjalankan skrip PHP sebagai daemon atau cronjob :#!/usr/bin/php -q

Ronald Conco
sumber
1

Gunakan fifo bernama.

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

Kemudian, kapan pun Anda ingin memulai tugas yang sudah berjalan lama, cukup tulis baris baru (bukan blokir ke file pemicu.

Selama input Anda lebih kecil dari PIPE_BUFdan ini adalah write()operasi tunggal , Anda dapat menulis argumen ke fifo dan membuatnya muncul seperti $REPLYdalam skrip.

geocar
sumber
1

tanpa menggunakan antrian, Anda dapat menggunakan yang proc_open()seperti ini:

    $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
sumber