PHP的exec命令(或类似命令)如何不等待结果?

91

我有一个命令需要运行,但我不希望PHP等待结果。

<?php
echo "Starting Script";
exec('run_baby_run');
echo "Thanks, Script is running in background";
?>

有没有可能让PHP不等待结果,即只是启动它并继续执行下一个命令。

我找不到任何相关的信息,也不确定是否可能。最好的方法可能是创建一个在一分钟后启动的CRON作业。


1
如果您需要频繁执行此操作,您可能会对类似Gearman的JobQueue感兴趣。 - Gordon
7个回答

147

根据文档

为了执行一个命令并且不会在运行时挂起你的PHP脚本,你运行的程序必须不向PHP输出。为此,请将stdout和stderr都重定向到/dev/null,然后将其放入后台。

> /dev/null 2>&1 &

为了执行一个命令并将其作为另一个进程启动,该进程不依赖于Apache线程来保持运行(如果有人取消页面,它不会死亡),请运行以下命令:

exec('bash -c "exec nohup setsid your_command > /dev/null 2>&1 &"');


4
非常感谢!有很多“解决方案”实际上并不起作用(主浏览器线程仍在等待)。许多解决方案只提到了结尾处的"&",这是不够的。话虽如此,对我来说,一个更简单的解决方案足够了:执行('nohup your_command > /dev/null 2>&1 &')。 - Vincent Pazeller
为什么您不需要将stdin也移植到/dev/null?进程的输入被链接到shell是否足以阻止nohup“放弃”子进程?(在此答案中,他们确实更改了stdin:http://superuser.com/questions/178587/how-do-i-detach-a-process-from-terminal-entirely) - NHDaly
另外,为什么你同时调用nohupsetsid?我找不到太多关于哪个更好或它们之间有何不同的信息。 - NHDaly
@Christin:您的解决方案适用于我的问题:https://dev59.com/UoPba4cB1Zd3GeqPoSh0 您能否在那个问题下发布一个答案。然后我就可以用一个绿色的答案关闭那个问题了。 - sugunan
使用setsid可以忽略死亡的父进程,使得使用nohup忽略死亡终端变得多余。 - Cees Timmerman
显示剩余5条评论

56

你可以在命令的末尾添加&将其放在后台运行,例如:

exec('run_baby_run &');

但仅仅这样做会使你的脚本挂起,因为:

如果使用exec函数启动程序,在程序继续在后台运行的情况下,必须将程序输出重定向到文件或另一个输出流中。未能这样做将导致PHP挂起,直到程序执行结束。

因此,你可以将命令的标准输出重定向到文件,以便稍后查看,或者重定向到/dev/null以将其丢弃,例如:

exec('run_baby_run > /dev/null &');

1
不重定向 stderr,并且会被崩溃的 Web 服务器杀死。 - Cees Timmerman

10

这使用wget向URL发送通知,无需等待。

$command = 'wget -qO- http://test.com/data=data';
exec('nohup ' . $command . ' >> /dev/null 2>&1 & echo $!', $pid);

使用"ls"命令更新日志,无需等待。

$command = 'ls -la > content.log';
exec('nohup ' . $command . ' >> /dev/null 2>&1 & echo $!', $pid);

3
我认为你在 2>&1echo 之间漏掉了一个 & 符号,我说的对吗? - santiago arizti
2
nohup 命令在父进程被杀死的情况下是否会保持子进程运行? - Cees Timmerman
@santiagoarizti 是的,我已经修复了,因为代码否则无法正常工作。 - Mr.Web

9

我知道这个问题已经被解答了,但是我在这里找到的答案对我的情况(或者对于Windows)无效。

我正在使用安装有PHP 7.2的Xampp v3.2.4的Windows 10笔记本电脑。

$command = 'php Cron.php send_email "'. $id .'"';
if ( substr(php_uname(), 0, 7) == "Windows" )
{
    //windows
    pclose(popen("start /B " . $command . " 1> temp/update_log 2>&1 &", "r"));
}
else
{
    //linux
    shell_exec( $command . " > /dev/null 2>&1 &" );
}

这对我来说完美地运作了。 希望这能帮助Windows用户。干杯。

谢谢,正是我所需要的。 - DrLightman
系统找不到指定的路径。 我认为temp/update_log这个不太清楚,你能解释一下吗? - rahul singh
@rahulsingh 那个文件路径只是来自于你当前的工作目录,也就是说,它假设你在当前目录下有一个名为“temp”的文件夹,并且该文件夹中有一个名为“upload_log”的文件,仅此而已。 - Anil Prz
1
这个稍微有点变化的方法在我的Windows上适用。 我使用了: pclose(popen("start /B " . $command . " 1> NUL 2>&1 &", "r")); 而不是: pclose(popen("start /B " . $command . " 1> temp/update_log 2>&1 &", "r")); 它可以正常工作。 - Nikhil Sinha

2
有两种实现方法。 最简单的方法是直接将结果发送到dev/null。
exec("run_baby_run > /dev/null 2>&1 &");

但是,如果您有其他操作需要执行,您可以考虑使用ignore_user_abort函数。

在使用该函数的情况下,即使您关闭连接,脚本仍将运行。


1
“exec nohup setsid your_command” nohup 命令允许 your_command 进程即使启动它的进程先于其终止也能继续运行。如果先于 your_command 进程终止,那么将会发送 SIGNUP 信号给 your_command 进程,导致该进程终止(除非它捕获并忽略该信号)。

根据这个答案,子进程必须注册SIGHUP以便在其父进程死亡时得到通知。然而,终端程序通常会在退出时发送HUP信号。 - Cees Timmerman

0

在Windows上,您可以使用COM对象:

if(class_exists('COM')) {
    $shell = new COM('WScript.Shell');
    $shell->Run($cmd, 1, false);
}
else {
    exec('nohup ' . $cmd . ' 2>&1 &');
}

PHP中没有COM类。 - Dimitrios Ververidis
确实有。我自己也使用它。https://www.php.net/manual/en/book.com.php - Benjamin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接