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() 等功能,但似乎没有一个提供我想要的功能。 (或者,如果他们这样做,我不清楚该怎么做。)有什么建议吗?

【问题讨论】:

无论您选择哪种解决方案,您还应该考虑使用niceionice 来防止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 脚本作为 daemoncronjob 运行:#!/usr/bin/php -q

【讨论】:

【参考方案6】:

如果它“不关心输出”,难道不能用 & 调用脚本的 exec 来后台进程吗?

编辑 - 合并 @AdamTheHut 对此帖子的评论,您可以将其添加到对 exec 的调用中:

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

这会将stdio(第一个>)和stderr2>)重定向到/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的使用权限并且你不能配置成,你可以尝试使用&lt;?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的主要内容,如果未能解决你的问题,请参考以下文章

PHP 中的 shell_exec()

PHP 中的 exec()、shell_exec、system() 和 passthru() 函数有啥不同? [复制]

PHP中的curl_exec

PHP shell_exec 等待脚本完成? [复制]

php生成pdf 下载 同步还是异步

无法使用PHP shell_exec执行powershell脚本函数