เชลล์แบบอะซิงโครนัส exec ใน PHP


199

ฉันมีสคริปต์ PHP ที่ต้องการเรียกใช้เชลล์สคริปต์ แต่ไม่สนใจเกี่ยวกับผลลัพธ์ เชลล์สคริปต์ทำการโทร SOAP จำนวนหนึ่งและทำงานช้าดังนั้นฉันไม่ต้องการชะลอคำขอ PHP ในขณะที่รอการตอบกลับ ในความเป็นจริงคำขอ PHP ควรจะสามารถออกได้โดยไม่ต้องยุติกระบวนการเชลล์

เราได้ตรวจสอบต่างๆexec(), shell_exec(), pcntl_fork()ฟังก์ชั่น ฯลฯ แต่ไม่มีของพวกเขาดูเหมือนจะนำเสนอสิ่งที่ฉันต้องการ (หรือถ้าพวกเขาทำมันไม่ชัดเจนสำหรับฉัน) คำแนะนำใด ๆ


ไม่ว่าคุณจะเลือกใช้โซลูชันแบบใดคุณควรพิจารณาใช้niceและioniceเพื่อป้องกันเชลล์สคริปต์ไม่ให้ระบบของคุณครอบงำ (เช่น/usr/bin/ionice -c3 /usr/bin/nice -n19)
rinogo

ซ้ำซ้อนที่เป็นไปได้ของPHP ดำเนินการกระบวนการพื้นหลัง
Sean the Bean

คำตอบ:


218

หาก "ไม่สนใจเอาต์พุต" จะไม่สามารถเรียกใช้ exec ไปยังสคริปต์ด้วย&พื้นหลังของกระบวนการได้หรือไม่

แก้ไข - รวมสิ่งที่ @ AdamTheHutแสดงความคิดเห็นในโพสต์นี้คุณสามารถเพิ่มสิ่งนี้ในการโทรไปที่exec:

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

ที่จะเปลี่ยนเส้นทางทั้งstdio(ก่อน>) และstderr( 2>) เพื่อ/dev/nullและทำงานในพื้นหลัง

มีวิธีอื่นในการทำสิ่งเดียวกัน แต่นี่เป็นวิธีที่ง่ายที่สุดในการอ่าน


ทางเลือกอื่นสำหรับการเปลี่ยนเส้นทางสองครั้งด้านบน:

" &> /dev/null &"

11
ดูเหมือนว่าจะใช้งานได้ แต่มันต้องการแอมป์แซนด์มากกว่าเล็กน้อย ฉันได้มันทำงานโดยการผนวก "> / dev / null 2> / dev / null &" เพื่อเรียก exec () แม้ว่าฉันจะต้องยอมรับว่าฉันไม่แน่ใจว่าสิ่งที่จะทำ
AdamTheHutt

2
วิธีไปแน่นอนถ้าคุณต้องการไฟและลืมด้วย php และ apache สภาพแวดล้อม Apache และ PHP ที่ใช้งานจริงจำนวนมากจะปิดการทำงาน pcntl_fork ()
วิธี

9
เพียงหมายเหตุสำหรับ&> /dev/null &xdebug จะไม่สร้างบันทึกหากคุณใช้สิ่งนี้ ตรวจสอบstackoverflow.com/questions/4883171/…
kapeels

5
@MichaelJMulligan มันปิดตัวให้คำอธิบายไฟล์ ที่กล่าวว่าแม้จะมีประสิทธิภาพเพิ่มขึ้นในการเข้าใจถึงปัญหาหลังการใช้/dev/nullเป็นวิธีปฏิบัติที่ดีกว่าเป็นเขียนไปยัง FDs ปิดทำให้เกิดข้อผิดพลาดในขณะที่พยายามอ่านหรือเขียน/dev/nullเพียงแค่ทำอะไรอย่างเงียบ ๆ
Charles Duffy

3
สคริปต์แบ็คกราวน์จะถูกฆ่าเมื่อรีสตาร์ท apache ... เพียงแค่ตระหนักถึงสิ่งนี้สำหรับงานที่ยาวนานมากหรือถ้าคุณโชคไม่ดีเวลาและคุณสงสัยว่าทำไมงานของคุณถึงหายไป ...
Julien

53

ผมใช้ที่นี้มันเป็นจริงที่เริ่มต้นกระบวนการอิสระ

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

4
ในบางสถานการณ์นี่เป็นทางออกที่ดีที่สุด มันเป็นคนเดียวที่ทำงานให้ฉันเพื่อปล่อย "sudo reboot" ("echo 'sleep 3; sudo reboot' | at now") จาก webgui และเสร็จสิ้นการเรนเดอร์หน้า .. ใน openbsd
Kaii

1
หากผู้ใช้ที่คุณเรียกใช้ apache (โดยปกติwww-data) ไม่มีสิทธิ์ใช้งานatและคุณไม่สามารถกำหนดค่าได้คุณสามารถลองใช้<?php exec('sudo sh -c "echo \"command\" | at now" ');หากcommandมีเครื่องหมายอัญประกาศให้ดูescapeshellargเพื่อช่วยให้คุณปวดหัว
Julien

1
การคิดเกี่ยวกับมันเพื่อให้ sudo เรียกใช้ sh ไม่ใช่ความคิดที่ดีที่สุดเพราะโดยทั่วไปแล้ว sudo จะช่วยให้เข้าถึงทุกสิ่งได้ ฉันหวนกลับไปใช้echo "sudo command" | at nowและแสดงความคิดเห็นwww-dataใน/etc/at.deny
Julien

@Julien บางทีคุณสามารถสร้างเชลล์สคริปต์ด้วยคำสั่ง sudo แล้วเปิดมันแทน ตราบใดที่คุณไม่ส่งค่าที่ผู้ใช้ส่งไปยังสคริปต์ดังกล่าว
Garet Claborn

26

สำหรับผู้ใช้ Windows ทุกคน: ฉันพบวิธีที่ดีในการเรียกใช้สคริปต์ PHP แบบอะซิงโครนัส (อันที่จริงมันใช้ได้กับเกือบทุกอย่าง)

มันขึ้นอยู่กับคำสั่ง popen () และ pclose () และทำงานได้ดีทั้งบน Windows และ Unix

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

รหัสดั้งเดิมจาก: http://php.net/manual/en/function.exec.php#86329


ใช้งานได้เฉพาะไฟล์เท่านั้นไม่สามารถใช้งานได้หากใช้ php / python / node ฯลฯ
david valentino

@davidvalentino ถูกต้องและก็ไม่เป็นไร! หากคุณต้องการเรียกใช้งานสคริปต์ PHP / Pyhton / NodeJS คุณต้องเรียกใช้งานจริงและส่งต่อให้กับสคริปต์ของคุณ ตัวอย่าง: คุณไม่ใส่ใน terminal ของคุณแต่คุณจะเขียนmyscript.js node myscript.jsนั่นคือ: nodeเป็นไฟล์เรียกทำงาน, myscript.jsเป็นสคริปต์ที่ทำงานได้ดี มีความแตกต่างอย่างมากระหว่างไฟล์เรียกทำงานและสคริปต์
LucaM

ถูกต้องที่ไม่มีปัญหาในกรณีนั้นในกรณีอื่น ๆ ตัวอย่างเช่นจำเป็นต้องเรียกใช้ laravel artisan php artisanเพียงแค่ใส่ความคิดเห็นที่นี่ดังนั้นไม่จำเป็นต้องติดตามว่าทำไมมันถึงไม่สามารถทำงานกับคำสั่งได้
david valentino

22

บน linux คุณสามารถทำสิ่งต่อไปนี้:

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

สิ่งนี้จะดำเนินการคำสั่งที่พร้อมรับคำสั่งจากนั้นเพียงส่งคืน PID ซึ่งคุณสามารถตรวจสอบ> 0 เพื่อให้แน่ใจว่ามันทำงานได้

คำถามนี้คล้ายกัน: PHP มีเธรดหรือไม่


คำตอบนี้จะง่ายต่อการอ่านถ้าคุณรวมเฉพาะสิ่งจำเป็นเปลือย (กำจัดaction=generate var1_id=23 var2_id=35 gen_id=535ส่วน) นอกจากนี้เนื่องจาก OP ถามเกี่ยวกับการเรียกใช้เชลล์สคริปต์คุณไม่จำเป็นต้องใช้บางส่วนของ PHP รหัสสุดท้ายจะเป็น:$cmd = 'nohup nice -n 10 /path/to/script.sh > /path/to/log/file.log & printf "%u" $!';
rinogo

นอกจากนี้ตามที่ทราบจากคนที่ได้ "มีมาก่อน" ทุกคนที่อ่านข้อความนี้อาจพิจารณาใช้ไม่เพียงแต่ยังnice ionice
rinogo

1
"% u" $ คืออะไร! ทำอะไร
Twigs

@Twigs &เรียกใช้รหัสก่อนหน้าในพื้นหลังจากนั้นprintfจะใช้สำหรับการส่งออกในรูปแบบของ$!ตัวแปรที่มี PID
NoChecksum

1
ขอบคุณมากฉันพยายามแก้ปัญหาทุกอย่างที่ฉันกำลังค้นหาเพื่อส่งออกไปยังบันทึกที่มีการเรียกเชลล์ async PHP และคุณเป็นคนเดียวที่ปฏิบัติตามเกณฑ์ทั้งหมด
Josh Powlison


6

ใน Linux คุณสามารถเริ่มต้นกระบวนการในเธรดอิสระใหม่โดยผนวกเครื่องหมายแอมเปอร์แซนด์และท้ายคำสั่ง

mycommand -someparam somevalue &

ใน Windows คุณสามารถใช้คำสั่ง DOS "เริ่มต้น"

start mycommand -someparam somevalue

2
บน Linux พาเรนต์ยังคงสามารถบล็อกได้จนกว่าเด็กจะเสร็จสิ้นการทำงานหากมันพยายามอ่านจากหมายเลขอ้างอิงไฟล์เปิดที่ถูกจัดการโดยกระบวนการย่อย (เช่น stdout) ดังนั้นนี่ไม่ใช่วิธีแก้ปัญหาที่สมบูรณ์
Charles Duffy

1
startคำสั่งทดสอบบน windows มันไม่ได้ทำงานแบบอะซิงโครนัส ... คุณสามารถรวมแหล่งที่มาที่คุณได้รับข้อมูลนั้นได้หรือไม่
Alph.Dev

@ Alph.Dev โปรดดูคำตอบของฉันหากคุณใช้ Windows: stackoverflow.com/a/40243588/1412157
LucaM

@mynameis คำตอบของคุณแสดงให้เห็นว่าทำไมคำสั่งเริ่มต้นไม่ทำงาน มันเป็นเพราะ/Bพารามิเตอร์ ฉันได้อธิบายไว้แล้วที่นี่: stackoverflow.com/a/34612967/1709903
Alph.Dev

6

วิธีที่ถูกต้อง (!) ที่จะทำคือการ

  1. ส้อม()
  2. setsid ()
  3. execve ()

ส้อมส้อม, setsid บอกกระบวนการปัจจุบันให้เป็นต้นแบบหนึ่ง (ไม่มีพาเรนต์), execve บอกให้กระบวนการเรียกถูกแทนที่ด้วยหนึ่งที่เรียกว่า เพื่อให้ผู้ปกครองสามารถออกจากโดยไม่ส่งผลกระทบต่อเด็ก

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

 // parent's stuff
 exit();

6
ปัญหาของ pcntl_fork () คือคุณไม่ควรใช้มันเมื่อทำงานภายใต้เว็บเซิร์ฟเวอร์เช่นเดียวกับ OP ทำ (นอกเหนือจาก OP ได้ลองแล้ว)
Guss

6

ฉันใช้สิ่งนี้ ...

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

(โดยที่PHP_PATHconst กำหนดเหมือนdefine('PHP_PATH', '/opt/bin/php5')หรือคล้ายกัน)

มันส่งผ่านข้อโต้แย้งผ่านทางบรรทัดคำสั่ง หากต้องการอ่านพวกเขาใน PHP, ดูargv


4

วิธีเดียวที่ฉันพบว่าทำงานได้จริงสำหรับฉันคือ:

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

3
'disown' เป็น Bash ในตัวและไม่ทำงานกับ shell_exec () ด้วยวิธีนี้ ฉันพยายามshell_exec("/usr/local/sbin/command.sh 2>&1 >/dev/null | at now & disown")และสิ่งที่ฉันได้รับคือ:sh: 1: disown: not found
โทมัส Daugaard

4

ฉันยังพบว่าSymfony Process Componentมีประโยชน์สำหรับสิ่งนี้

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

ดูวิธีการทำงานของมันในrepo GitHub


2
คำเตือน: หากคุณไม่รอกระบวนการจะถูกฆ่าเมื่อการร้องขอสิ้นสุด
the_nuts

เครื่องมือที่สมบูรณ์แบบที่ขึ้นอยู่กับproc_*ฟังก์ชั่นภายใน
MAChitgarha


1

ใช้ชื่อ Fifo

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

จากนั้นเมื่อใดก็ตามที่คุณต้องการเริ่มงานที่ใช้เวลานานเพียงแค่เขียนขึ้นบรรทัดใหม่ (การปิดกั้นไปยังไฟล์ทริกเกอร์

ตราบใดที่การป้อนข้อมูลของคุณมีขนาดเล็กกว่าPIPE_BUFและเป็นการwrite()ดำเนินการเพียงครั้งเดียวคุณสามารถเขียนอาร์กิวเมนต์ลงใน Fifo และให้ปรากฏขึ้น$REPLYในสคริปต์


1

โดยไม่ต้องใช้คิวคุณสามารถใช้สิ่งต่อไป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);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.