PHP中的异步shell exec
Posted
技术标签:
【中文标题】PHP中的异步shell exec【英文标题】:Asynchronous shell exec in PHP 【发布时间】:2010-09-18 08:31:59 【问题描述】:我有一个 php 脚本,它需要调用一个 shell 脚本,但根本不关心输出。 shell 脚本进行了许多 SOAP 调用并且完成速度很慢,所以我不想在 PHP 请求等待回复时减慢它的速度。事实上,PHP 请求应该能够在不终止 shell 进程的情况下退出。
我研究了各种exec()
、shell_exec()
、pcntl_fork()
等功能,但似乎没有一个提供我想要的功能。 (或者,如果他们这样做,我不清楚该怎么做。)有什么建议吗?
【问题讨论】:
无论您选择哪种解决方案,您还应该考虑使用nice
和ionice
来防止shell 脚本淹没您的系统(例如/usr/bin/ionice -c3 /usr/bin/nice -n19
)
php execute a background process的可能重复
【参考方案1】:
我还发现 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 repo 中查看它的工作原理。
【讨论】:
警告:如果不等待,请求结束时进程会被杀死 完美工具,基于proc_*
内部函数。【参考方案2】:
在 linux 上,您可以执行以下操作:
$cmd = 'nohup nice -n 10 php -f php/file.php > log/file.log & printf "%u" $!';
$pid = shell_exec($cmd);
这将在命令提示符处执行命令,然后只返回 PID,您可以检查它是否 > 0 以确保它有效。
这个问题类似:Does PHP have threading?
【讨论】:
如果您只包含最基本的内容(删除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" $!';
另外,作为“曾经去过那里”的人的注释,任何阅读本文的人可能会考虑不仅使用nice
,还可以使用ionice
。
什么是 "%u" $!具体做什么?
@Twigs &
在后台运行前面的代码,然后 printf
用于格式化输出包含 PID 的 $!
变量
非常感谢,我尝试了各种解决方案,找到了通过异步 PHP shell 调用输出到日志的解决方案,而您的解决方案是唯一满足所有条件的解决方案。【参考方案3】:
不使用队列,你可以像这样使用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 ***.com';
$process = proc_open($command, $descriptorspec, $pipes);
【讨论】:
【参考方案4】:致所有 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 等则不起作用 @davidvalentino 正确,没关系!如果您想执行 PHP/Pyhton/NodeJS 脚本,您必须实际调用可执行文件并将您的脚本传递给它。例如:您不要输入终端myscript.js
,而是输入node myscript.js
。也就是说:node 是可执行文件,myscript.js 是要执行的脚本。可执行文件和脚本之间存在巨大差异。
在这种情况下没问题,在其他情况下,例如需要运行 laravel artisan,php artisan
只需在此处添加评论,因此无需跟踪为什么它不能与命令一起使用
短小精悍,它可以工作(Windows + Wamp - 运行命令:execInBackground("curl".$url);【参考方案5】:
您也可以将 PHP 脚本作为 daemon 或 cronjob 运行:#!/usr/bin/php -q
【讨论】:
【参考方案6】:如果它“不关心输出”,难道不能用 &
调用脚本的 exec 来后台进程吗?
编辑 - 合并 @AdamTheHut 对此帖子的评论,您可以将其添加到对 exec
的调用中:
" > /dev/null 2>/dev/null &"
这会将stdio
(第一个>
)和stderr
(2>
)重定向到/dev/null
并在后台运行。
还有其他方法可以做同样的事情,但这是最简单的阅读方式。
上述双重重定向的替代方案:
" &> /dev/null &"
【讨论】:
这似乎可行,但它需要的不仅仅是一个&符号。我通过将 "> /dev/null 2>/dev/null &" 附加到 exec() 调用来使其工作。虽然我不得不承认我不确定那是做什么的。 如果您想使用 php 和 apache 就火而忘掉,这绝对是您的必经之路。许多生产 Apache 和 PHP 环境将禁用 pcntl_fork()。 请注意&> /dev/null &
,如果您使用它,xdebug 不会生成日志。检查***.com/questions/4883171/…
@MichaelJMulligan 它关闭文件描述符。也就是说,尽管提高了效率,但事后看来,使用 /dev/null
是更好的做法,因为写入已关闭的 FD 会导致错误,而尝试读取或写入 /dev/null
只是默默地什么都不做。
后台脚本将在重启 apache 时被杀死......请注意这对于很长的工作,或者如果你在时间上不走运并且你想知道为什么你的工作消失了......【参考方案7】:
我发现真正对我有用的唯一方法是:
shell_exec('./myscript.php | at now & disown')
【讨论】:
'disown' 是 Bash 内置的,不能以这种方式与 shell_exec() 一起使用。我试过shell_exec("/usr/local/sbin/command.sh 2>&1 >/dev/null | at now & disown")
,我得到的是:sh: 1: disown: not found
【参考方案8】:
我为此使用了 at,因为它确实开始了一个独立的过程。
<?php
`echo "the command"|at now`;
?>
【讨论】:
在某些情况下,这绝对是最好的解决方案。它是唯一一个对我有用的从 webgui 发布“sudo reboot”(“echo 'sleep 3; sudo reboot' | at now”)并完成在 openbsd 上渲染页面.. 如果你运行apache的用户(一般是www-data
)没有at
的使用权限并且你不能配置成,你可以尝试使用<?php exec('sudo sh -c "echo \"command\" | at now" ');
如果@ 987654326@ 包含引号,请参阅escapeshellarg 以节省您的头疼
仔细想想,让 sudo 运行 sh 并不是最好的主意,因为它基本上为 sudo 提供了对所有内容的 root 访问权限。我恢复使用echo "sudo command" | at now
并在/etc/at.deny
中评论www-data
@Julien 也许您可以使用 sudo 命令创建一个 shell 脚本并启动它。只要您没有将任何用户提交的值传递给所述脚本
at
包(和命令)对于某些 Linux 发行版来说是非默认的。它还需要运行atd
服务。由于缺乏解释,此解决方案可能难以理解。【参考方案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 &");
(其中PHP_PATH
是一个类似define('PHP_PATH', '/opt/bin/php5')
或类似定义的常量)
它通过命令行传递参数。要在 PHP 中阅读它们,请参阅 argv。
【讨论】:
【参考方案10】:使用命名的先进先出。
#!/bin/sh
mkfifo trigger
while true; do
read < trigger
long_running_task
done
然后,每当您想启动长时间运行的任务时,只需写一个换行符(非阻塞到触发器文件。
只要您的输入小于PIPE_BUF
并且是单个write()
操作,您就可以将参数写入fifo 并让它们在脚本中显示为$REPLY
。
【讨论】:
【参考方案11】:正确的方法(!)是
-
fork()
setsid()
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();
【讨论】:
pcntl_fork() 的问题是你不应该像 OP 那样在 Web 服务器下运行时使用它(此外,OP 已经尝试过了)。【参考方案12】:在 Linux 中,您可以通过在命令末尾附加一个 & 符号来在新的独立线程中启动一个进程
mycommand -someparam somevalue &
在 Windows 中,您可以使用“启动”DOS 命令
start mycommand -someparam somevalue
【讨论】:
在 Linux 上,如果父进程试图从子进程持有的打开文件句柄(即标准输出)中读取,父进程仍然可以阻塞直到子进程完成运行,所以这不是一个完整的解决方案. 在 Windows 上测试了start
命令,它不会异步运行...您能否提供您从中获取该信息的来源?
@Alph.Dev 如果您使用的是 Windows,请查看我的回答:***.com/a/40243588/1412157
@mynameis 您的回答准确地说明了启动命令不起作用的原因。这是因为/B
参数。我在这里解释过:***.com/a/34612967/1709903【参考方案13】:
php-execute-a-background-process 有一些很好的建议。我认为我的还不错,但我有偏见:)
【讨论】:
以上是关于PHP中的异步shell exec的主要内容,如果未能解决你的问题,请参考以下文章