Thực thi shell không đồng bộ trong PHP


199

Tôi đã có một tập lệnh PHP cần gọi một tập lệnh shell nhưng hoàn toàn không quan tâm đến đầu ra. Kịch bản shell thực hiện một số cuộc gọi SOAP và chậm hoàn thành, vì vậy tôi không muốn làm chậm yêu cầu PHP trong khi nó chờ phản hồi. Trong thực tế, yêu cầu PHP sẽ có thể thoát mà không kết thúc quá trình shell.

Tôi đã nhìn vào khác nhau exec(), shell_exec(), pcntl_fork(), vv chức năng, nhưng không ai trong số họ dường như để cung cấp chính xác những gì tôi muốn. (Hoặc, nếu họ làm, nó không rõ ràng với tôi như thế nào.) Có gợi ý nào không?


Cho dù bạn chọn giải pháp nào, bạn cũng nên cân nhắc sử dụng niceioniceđể ngăn tập lệnh shell áp đảo hệ thống của bạn (ví dụ /usr/bin/ionice -c3 /usr/bin/nice -n19)
rinogo

Bản sao có thể có của php thực hiện quy trình nền
Sean the Bean

Câu trả lời:


218

Nếu nó "không quan tâm đến đầu ra", thì lệnh thực thi tập lệnh có thể được gọi với &nền để xử lý không?

EDIT - kết hợp những gì @ AdamTheHut đã nhận xét cho bài đăng này, bạn có thể thêm nó vào một cuộc gọi đến exec:

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

Điều đó sẽ chuyển hướng cả stdio(đầu tiên >) và stderr( 2>) đến /dev/nullvà chạy trong nền.

Có nhiều cách khác để làm điều tương tự, nhưng đây là cách đơn giản nhất để đọc.


Một thay thế cho chuyển hướng kép ở trên:

" &> /dev/null &"

11
Điều này có vẻ hiệu quả, nhưng nó cần nhiều hơn một chút dấu và. Tôi đã làm cho nó hoạt động bằng cách nối thêm "> / dev / null 2> / dev / null &" vào lệnh gọi exec (). Mặc dù tôi phải thừa nhận rằng tôi không chắc chắn điều đó.
AdamTheHutt

2
Chắc chắn là con đường để đi nếu bạn muốn lửa và quên đi với php và apache. Rất nhiều môi trường sản xuất Apache và PHP sẽ bị vô hiệu hóa pcntl_fork ().
phương pháp

9
Chỉ cần một lưu ý cho &> /dev/null &, xdebug sẽ không tạo nhật ký nếu bạn sử dụng điều này. Kiểm tra stackoverflow.com/questions/4883171/ từ
kapeels

5
@MichaelJMulligan nó đóng mô tả tập tin. Điều đó nói rằng, mặc dù đạt được hiệu quả, nhưng nhìn chung, sử dụng /dev/nulllà cách thực hành tốt hơn, vì viết vào các FD đóng gây ra lỗi, trong khi cố gắng đọc hoặc viết để /dev/nullchỉ im lặng không làm gì cả.
Charles Duffy

3
Các kịch bản nền sẽ bị giết khi khởi động lại apache ... chỉ cần lưu ý điều này cho các công việc rất dài hoặc nếu bạn không may mắn về thời gian và bạn tự hỏi tại sao công việc của bạn biến mất ...
Julien

53

Tôi đã từng cho điều này, vì nó thực sự đang bắt đầu một quá trình độc lập.

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

4
trong một số tình huống đây hoàn toàn là giải pháp tốt nhất. đó là người duy nhất làm việc cho tôi phát hành "sudo restart" ("echo 'ngủ 3; sudo restart' | at now") từ webgui VÀ kết thúc hiển thị trang .. trên openbsd
Kaii

1
nếu người dùng bạn chạy apache (thường www-data) không có quyền sử dụng atvà bạn không thể định cấu hình nó, bạn có thể thử sử dụng <?php exec('sudo sh -c "echo \"command\" | at now" ');Nếu commandcó dấu ngoặc kép, hãy xem escapeshellarg để cứu bạn khỏi đau đầu
Julien

1
Nghĩ kỹ về nó, để có được sudo để chạy sh không phải là ý tưởng tốt nhất, vì về cơ bản nó cho sudo quyền truy cập vào mọi thứ. Tôi đã hoàn nguyên để sử dụng echo "sudo command" | at nowvà nhận xét www-datatrong/etc/at.deny
Julien

@Julien có lẽ bạn có thể tạo một kịch bản shell bằng lệnh sudo và khởi chạy nó thay thế. miễn là bạn không chuyển bất kỳ giá trị nào của người dùng vào tập lệnh đã nói
Garet Claborn

26

Đối với tất cả người dùng Windows: Tôi đã tìm thấy một cách tốt để chạy tập lệnh PHP không đồng bộ (thực sự nó hoạt động với hầu hết mọi thứ).

Nó dựa trên các lệnh popen () và pclose (). Và hoạt động tốt cả trên Windows và Unix.

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

Mã gốc từ: http://php.net/manual/en/feft.exec.php#86329


Điều này chỉ thực thi một tệp, không hoạt động nếu sử dụng php / python / node, v.v.
david valentino

@davidvalentino Đúng, và thế là tốt! Nếu bạn muốn thực thi một tập lệnh PHP / Pyhton / NodeJS, bạn phải thực sự gọi tập lệnh thực thi và chuyển cho tập lệnh của bạn. Ví dụ: bạn không đặt trong thiết bị đầu cuối của mình myscript.jsmà thay vào đó, bạn sẽ viết node myscript.js. Đó là: nút là tập tin thực thi, myscript.js là tập lệnh, tốt, để thực thi. Có một sự khác biệt rất lớn giữa thực thi và kịch bản.
LucaM

đúng là không có vấn đề gì trong trường hợp đó ,, trong các trường hợp khác, ví dụ như cần chạy nghệ nhân laravel, php artisanchỉ cần đặt bình luận ở đây để không cần truy tìm lý do tại sao nó không hoạt động với các lệnh
david valentino

22

Trên linux bạn có thể làm như sau:

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

Điều này sẽ thực thi lệnh tại dấu nhắc lệnh và sau đó chỉ cần trả về PID, mà bạn có thể kiểm tra> 0 để đảm bảo nó hoạt động.

Câu hỏi này tương tự: PHP có phân luồng không?


Câu trả lời này sẽ dễ đọc hơn nếu bạn chỉ bao gồm các yếu tố cần thiết (loại bỏ action=generate var1_id=23 var2_id=35 gen_id=535phân khúc). Ngoài ra, vì OP đã hỏi về việc chạy tập lệnh shell, bạn không cần các phần dành riêng cho PHP. Mã cuối cùng sẽ là:$cmd = 'nohup nice -n 10 /path/to/script.sh > /path/to/log/file.log & printf "%u" $!';
rinogo

Ngoài ra, như một lưu ý từ một người đã "ở đó trước đây", bất kỳ ai đọc nó cũng có thể cân nhắc sử dụng không chỉ nicemà còn ionice.
rinogo

1
"% U" $ là gì! làm chính xác?
Twigs

@Twigs &chạy mã trước trong nền, sau đó printfđược sử dụng cho đầu ra được định dạng của $!biến chứa PID
NoChecksum

1
Cảm ơn bạn rất nhiều, tôi đã thử tất cả các loại giải pháp mà tôi đang tìm để xuất ra một bản ghi với một cuộc gọi shell PHP không đồng bộ và bạn là người duy nhất đáp ứng tất cả các tiêu chí.
Josh Powlison


6

Trong Linux, bạn có thể bắt đầu một quy trình trong một luồng độc lập mới bằng cách nối thêm dấu và ở cuối lệnh

mycommand -someparam somevalue &

Trong Windows, bạn có thể sử dụng lệnh "bắt đầu" DOS

start mycommand -someparam somevalue

2
Trên Linux, cha mẹ vẫn có thể chặn cho đến khi đứa trẻ chạy xong nếu nó cố đọc từ một tệp xử lý mở được giữ bởi quy trình con (ví dụ: stdout), vì vậy đây không phải là một giải pháp hoàn chỉnh.
Charles Duffy

1
Đã kiểm tra startlệnh trên windows, nó không chạy không đồng bộ ... Bạn có thể bao gồm nguồn nơi bạn lấy thông tin đó không?
Alph.Dev

@ Alph.Dev vui lòng xem câu trả lời của tôi nếu bạn đang sử dụng Windows: stackoverflow.com/a/40243588/1412157
LucaM

@mynameis Câu trả lời của bạn cho thấy chính xác lý do tại sao lệnh start không hoạt động. Đó là vì /Btham số. Tôi đã giải thích nó ở đây: stackoverflow.com/a/34612967/1709903
Alph.Dev

6

cách đúng (!) để làm điều đó là

  1. cái nĩa()
  2. setid ()
  3. thực hiện ()

fork forks, setsid nói cho quá trình hiện tại trở thành một master (không có cha mẹ), thực hiện nói cho quá trình gọi được thay thế bởi một cuộc gọi. để cha mẹ có thể bỏ việc mà không ảnh hưởng đến trẻ.

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

 // parent's stuff
 exit();

6
Vấn đề với pcntl_fork () là bạn không được phép sử dụng nó khi chạy dưới máy chủ web, như OP đã làm (bên cạnh đó, OP đã thử điều này rồi).
Guss

6

Tôi đã sử dụng ...

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

(trong đó PHP_PATHmột const được định nghĩa như define('PHP_PATH', '/opt/bin/php5')hoặc tương tự)

Nó truyền trong các đối số thông qua dòng lệnh. Để đọc chúng trong PHP, xem argv .


4

Cách duy nhất mà tôi thấy thực sự hiệu quả với tôi là:

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

3
'disown' là một Bash tích hợp và không hoạt động với shell_exec () theo cách này. Tôi đã thử shell_exec("/usr/local/sbin/command.sh 2>&1 >/dev/null | at now & disown")và tất cả những gì tôi nhận được là:sh: 1: disown: not found
Thomas Daugaard

4

Tôi cũng thấy Thành phần quá trình Symfony hữu ích cho việc này.

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

Xem cách nó hoạt động trong repo GitHub của nó .


2
Cảnh báo: nếu bạn không chờ đợi, quy trình sẽ bị
hủy

Công cụ hoàn hảo, đó là dựa trên proc_*các chức năng nội bộ.
MAChitgarha

2

Bạn cũng có thể chạy tập lệnh PHP dưới dạng daemon hoặc cronjob :#!/usr/bin/php -q


1

Sử dụng một fifo được đặt tên.

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

Sau đó, bất cứ khi nào bạn muốn bắt đầu tác vụ chạy dài, chỉ cần viết một dòng mới (bỏ chặn vào tệp kích hoạt.

Miễn là đầu vào của bạn nhỏ hơn PIPE_BUFvà đó là một write()thao tác duy nhất , bạn có thể viết các đối số vào fifo và để chúng hiển thị như $REPLYtrong tập lệnh.


1

không sử dụng hàng đợi, bạn có thể sử dụng proc_open()như thế này:

    $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);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.