PHP 中的异步 shell exec

Asynchronous shell exec in PHP

提问人:AdamTheHutt 提问时间:10/22/2008 最后编辑:BoazAdamTheHutt 更新时间:9/13/2022 访问量:191286

问:

我有一个PHP脚本,它需要调用shell脚本,但根本不关心输出。shell 脚本会进行许多 SOAP 调用,并且完成速度很慢,因此我不想在等待回复时减慢 PHP 请求的速度。事实上,PHP 请求应该能够在不终止 shell 进程的情况下退出。

我研究了各种 、 、 等函数,但它们似乎都没有提供我想要的。(或者,如果他们这样做了,我不清楚是怎么回事。有什么建议吗?exec()shell_exec()pcntl_fork()

PHP 异步 shell

评论

0赞 rinogo 8/3/2017
无论您选择哪种解决方案,您都应该考虑使用 和 防止 shell 脚本使您的系统不堪重负(例如niceionice/usr/bin/ionice -c3 /usr/bin/nice -n19)
1赞 Sean the Bean 2/1/2018
php 执行后台进程的可能重复项

答:

239赞 warren 10/22/2008 #1

如果它“不关心输出”,那么脚本的 exec 是否不能在后台调用进程?&

编辑 - 结合@AdamTheHut对这篇文章的评论,您可以将其添加到以下调用中:exec

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

这会将 (first ) 和 () 重定向到后台并在后台运行。stdio>stderr2>/dev/null

还有其他方法可以做同样的事情,但这是最简单的阅读方法。


上述双重重定向的替代方法:

" &> /dev/null &"

评论

11赞 AdamTheHutt 10/22/2008
这似乎有效,但它需要的不仅仅是一个 & 符号。我通过将“> /dev/null 2>/dev/null &”附加到 exec() 调用来使其工作。虽然我不得不承认,我并不完全确定这是做什么的。
2赞 method 8/20/2009
如果你想用php和apache开火并忘记,绝对是要走的路。许多生产 Apache 和 PHP 环境将禁用 pcntl_fork()。
9赞 kapeels 2/4/2011
请注意,如果您使用它,xdebug 将不会生成日志。检查 stackoverflow.com/questions/4883171/...&> /dev/null &
5赞 Charles Duffy 10/22/2013
@MichaelJMulligan,它会关闭文件描述符。也就是说,尽管效率有所提高,但事后看来,使用是更好的做法,因为写入封闭的 FD 会导致错误,而尝试读取或写入只是静默地执行任何操作。/dev/null/dev/null
3赞 Julien 4/9/2015
重新启动apache时后台脚本将被杀死...对于很长的工作,或者如果你在时机上不走运,你想知道为什么你的工作消失了......
58赞 Czimi 10/22/2008 #2

我用过 at,因为它确实启动了一个独立的过程。

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

评论

4赞 Kaii 8/14/2009
在某些情况下,这绝对是最好的解决方案。它是唯一一个对我有用的发布“sudo reboot”(“echo 'sleep 3;sudo 重启' |现在“),并完成页面的渲染。在 OpenBSD 上
1赞 Julien 4/9/2015
如果你运行apache的用户(通常)没有权限使用,你无法配置它,你可以尝试使用如果包含引号,请参阅 escapeshellarg 以省去麻烦www-dataat<?php exec('sudo sh -c "echo \"command\" | at now" ');command
1赞 Julien 4/16/2015
仔细想想,让 sudo 运行 sh 并不是最好的主意,因为它基本上为 sudo 提供了对所有内容的 root 访问权限。我恢复了使用和注释echo "sudo command" | at nowwww-data/etc/at.deny
0赞 That Realty Programmer Guy 4/3/2018
@Julien也许您可以使用 sudo 命令制作一个 shell 脚本并启动它。只要您没有将任何用户提交的值传递给所述脚本
1赞 rzlvmp 11/9/2020
at对于某些 Linux 发行版,package (和 command) 不是默认的。此外,它需要运行服务。由于缺乏解释,此解决方案可能难以理解。atd
26赞 Darryl Hein 10/22/2008 #3

在 linux 上,您可以执行以下操作:

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

这将在命令提示符下执行命令,然后只返回 PID,您可以检查 > 0 以确保它正常工作。

这个问题是类似的:PHP有线程吗?

评论

0赞 rinogo 8/3/2017
如果您只包含最基本的要素(删除该段),则此答案将更容易阅读。此外,由于 OP 询问了运行 shell 脚本的问题,因此您不需要特定于 PHP 的部分。最终代码为:action=generate var1_id=23 var2_id=35 gen_id=535$cmd = 'nohup nice -n 10 /path/to/script.sh > /path/to/log/file.log & printf "%u" $!';
0赞 rinogo 8/3/2017
此外,作为“以前去过那里”的人的注释,任何阅读本文的人都可能会考虑不仅使用 ,而且使用 .niceionice
1赞 MortiestMorty 10/25/2017
“%u”$!究竟是吗?
0赞 NoChecksum 1/14/2019
@Twigs在后台运行前面的代码,然后用于包含 PID 的变量的格式化输出&printf$!
1赞 Josh Powlison 12/30/2019
非常感谢,我尝试了我发现的各种解决方案,以使用异步 PHP shell 调用输出到日志中,而您的解决方案是唯一满足所有条件的解决方案。
13赞 Mark Biek 10/22/2008 #4

php-execute-a-background-process 有一些很好的建议。我认为我的很好,但我有偏见:)

6赞 Leo 10/22/2008 #5

在 Linux 中,您可以通过在命令末尾附加一个 & 符号,在新的独立线程中启动进程

mycommand -someparam somevalue &

在 Windows 中,您可以使用“start”DOS 命令

start mycommand -someparam somevalue

评论

2赞 Charles Duffy 5/17/2012
在 Linux 上,如果父进程尝试从子进程(即 stdout)持有的打开文件句柄读取数据,则在子进程完成运行之前,父进程仍然可以阻止,因此这不是一个完整的解决方案。
1赞 Slava 1/4/2016
在 Windows 上测试了命令,它不会异步运行...您能否包括您获得该信息的来源?start
0赞 LucaM 10/25/2016
@Alph.Dev 如果您使用的是 Windows,请查看我的答案: stackoverflow.com/a/40243588/1412157
0赞 Slava 10/28/2016
@mynameis 您的回答确切地显示了启动命令不起作用的原因。这是因为参数。我在这里解释过:stackoverflow.com/a/34612967/1709903/B
2赞 Ronald Conco 11/7/2008 #6

您也可以将 PHP 脚本作为守护程序cronjob 运行:#!/usr/bin/php -q

6赞 pierre_moon 11/14/2008 #7

正确的方法是

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

fork forks,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赞 Guss 5/11/2010
pcntl_fork() 的问题在于,在 Web 服务器下运行时,您不应该像 OP 那样使用它(此外,OP 已经尝试过了)。
1赞 geocar 11/14/2008 #8

使用命名的 fifo。

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

然后,每当您想启动长时间运行的任务时,只需在触发器文件中写入换行符(非阻塞)。

只要您的输入小于并且是单个操作,您就可以将参数写入 fifo 并让它们显示在脚本中。PIPE_BUFwrite()$REPLY

6赞 philfreo 7/20/2010 #9

我用了这个...

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

(其中 const 定义相似或相似)PHP_PATHdefine('PHP_PATH', '/opt/bin/php5')

它通过命令行传入参数。要在 PHP 中读取它们,请参阅 argv

4赞 Gordon Forsythe 2/9/2012 #10

我发现真正对我有用的唯一方法是:

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

评论

3赞 Thomas Daugaard 1/15/2013
'disown' 是 Bash 内置的,不能以这种方式与 shell_exec() 一起使用。我试过了,我得到的只是:shell_exec("/usr/local/sbin/command.sh 2>&1 >/dev/null | at now & disown")sh: 1: disown: not found
39赞 LucaM 10/25/2016 #11

致所有 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

评论

0赞 david valentino 1/16/2020
这只能执行一个文件,如果使用 php/python/node 等,则不起作用
2赞 LucaM 1/16/2020
@davidvalentino 没错,没关系!如果你想执行一个 PHP/Pyhton/NodeJS 脚本,你必须实际调用可执行文件并将你的脚本传递给它。例如:你不输入你的终端,而是写.也就是说:node 是可执行文件,myscript.js 是要执行的脚本。可执行文件和脚本之间存在巨大差异。myscript.jsnode myscript.js
0赞 david valentino 1/17/2020
是的,在这种情况下没有问题,在其他情况下,例如需要运行 Laravel Artisan,只需在此处添加注释,因此无需跟踪为什么它不适用于命令php artisan
0赞 Rick 10/24/2021
简短,甜蜜,有效(Windows + Wamp - 运行命令:execInBackground(“curl”.$url);
1赞 LF00 12/21/2016 #12

如果没有 use queue,您可以像这样使用 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);
5赞 Anton Pelykh 10/11/2017 #13

我还发现 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

在其 GitHub 存储库中查看它的工作原理。

评论

2赞 the_nuts 1/9/2019
警告:如果不等待,该进程将在请求结束时终止
0赞 MAChitgarha 1/29/2019
完美的工具,即基于内部功能。proc_*
0赞 Kamshory 9/13/2022 #14

我不能在 Windows 上使用,所以我改用。我在 Windows 7.4.23 上运行 PHP 11。 > /dev/null 2>/dev/null &proc_open

这是我的代码。


function run_php_async($value, $is_windows)
{
    if($is_windows)
    {
        $command = 'php -q '.$value." ";
        echo 'COMMAND '.$command."\r\n";
        proc_open($command, [], $pipe);    
    }
    else
    {
        $command = 'php -q '.$value." > /dev/null 2>/dev/null &";
        echo 'COMMAND '.$command."\r\n";
        shell_exec($command);    
    }
}
$tasks = array();

$tasks[] = 'f1.php';
$tasks[] = 'f2.php';
$tasks[] = 'f3.php';
$tasks[] = 'f4.php';
$tasks[] = 'f5.php';
$tasks[] = 'f6.php';

$is_windows = true;

foreach($tasks as $key=>$value)
{
    run_php_async($value, $is_windows);
    echo 'STARTED AT '.date('H:i:s')."\r\n";
}

在每个要执行的文件中,我都把 delay 这个:

<?php
sleep(mt_rand(1, 10));
file_put_contents(__FILE__.".txt", time());

所有文件都是异步执行的。