PHP中的异步shell exec


199

我有一个PHP脚本,该脚本需要调用shell脚本,但根本不关心输出。Shell脚本会进行许多SOAP调用,并且完成起来很慢,因此我不想在等待回复时放慢PHP请求。实际上,PHP请求应该能够退出而不终止shell进程。

我已经研究过的各种exec()shell_exec()pcntl_fork(),等功能,但他们都不似乎提供正是我想要的。(或者,如果这样做,我不清楚如何做到。)有什么建议吗?


无论选择哪种解决方案,都应该考虑使用nice并且ionice防止shell脚本淹没您的系统(例如/usr/bin/ionice -c3 /usr/bin/nice -n19
rinogo

Answers:


218

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

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

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

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

还有其他方法可以执行相同的操作,但这是最简单的方法。


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

" &> /dev/null &"

11
这似乎可行,但是只需要一个“&”号即可。我通过将“> / dev / null 2> / dev / null&”附加到exec()调用来使其工作。尽管我不得不承认我不确定这是什么。
08年

2
如果您想用php和apache忘却火,那绝对是必经之路。许多生产Apache和PHP环境将禁用pcntl_fork()。
方法

9
仅针对的注释&> /dev/null &,如果使用xdebug,则不会生成日志。检查stackoverflow.com/questions/4883171/...
kapeels

5
@MichaelJMulligan将关闭文件描述符。就是说,尽管效率有所提高,但事后看来,/dev/null还是最好的做法,因为写入封闭的FD会导致错误,而尝试进行读取或写入/dev/null只是简单地无声无息。
查尔斯·达菲

3
重新启动apache时,后台脚本将被杀死……仅是在很长的工作时间内意识到这一点,或者如果您的时间安排不巧,您会奇怪为什么您的工作消失了?
Julien 2015年

53

我曾经这一点,因为它是真正开始独立的进程。

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

4
在某些情况下,这绝对是最好的解决方案。这是唯一对我
有用的

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也许您可以使用sudo命令制作一个shell脚本并启动它。只要您没有将任何用户提交的值传递给所述脚本
-Garet Claborn 18'Apr

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脚本,则必须实际调用可执行文件并将其传递给脚本。例如:您没有将终端放进去,myscript.js而是写了node myscript.js。也就是说:node是可执行文件,myscript.js是要执行的脚本。可执行文件和脚本之间存在巨大差异。
LucaM

没错,在这种情况下没问题,在其他情况下,例如需要运行laravel artisan的示例,php artisan只需在此处添加注释,这样就无需跟踪其为何无法与命令一起使用
大卫·瓦伦蒂诺

22

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

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

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

这个问题类似:PHP是否具有线程?


如果仅包含基本要点(消除了该action=generate var1_id=23 var2_id=35 gen_id=535部分),则此答案将更易于阅读。另外,由于OP询问有关运行Shell脚本的问题,因此您不需要特定于PHP的部分。最终代码为:$cmd = 'nohup nice -n 10 /path/to/script.sh > /path/to/log/file.log & printf "%u" $!';
rinogo '17

此外,从一个谁也记“去过那儿”,任何人读这可能会考虑使用不仅仅是niceionice
rinogo

1
“%u” $是什么!确切地做?
树枝

@Twigs &在后台运行前面的代码,然后printf用于$!包含PID 的变量的格式化输出
NoChecksum

1
非常感谢,我尝试了各种解决方案,发现它们可以通过异步PHP Shell调用输出到日志,而您的解决方案是唯一满足所有条件的解决方案。
乔什·普利森


6

在Linux中,您可以通过在命令末尾附加与号来在新的独立线程中启动进程。

mycommand -someparam somevalue &

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

start mycommand -someparam somevalue

2
在Linux上,如果父级试图从子进程(例如stdout)持有的打开文件句柄中读取,则父级仍可以阻塞直到子级完成运行,因此这不是一个完整的解决方案。
查尔斯·达菲

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()

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
pcntl_fork()的问题在于,在Web服务器下运行时不应该像OP那样使用它(此外,OP已经尝试过了)。
格斯

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
Thomas Daugaard 2013年

4

我还发现Symfony流程组件对此很有用。

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

完善的工具,基于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);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.