【问题标题】:Asynchronous shell exec in PHPPHP中的异步shell exec
【发布时间】: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 asynchronous shell


【解决方案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 stackoverflow.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 不会生成日志。检查stackoverflow.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】:

          正确的方法(!)是

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

          【讨论】:

          • pcntl_fork() 的问题是你不应该像 OP 那样在 Web 服务器下运行时使用它(此外,OP 已经尝试过了)。
          【解决方案12】:

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

          mycommand -someparam somevalue &
          

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

          start mycommand -someparam somevalue
          

          【讨论】:

          • 在 Linux 上,如果父进程试图从子进程持有的打开文件句柄(即标准输出)中读取,父进程仍然可以阻塞直到子进程完成运行,所以这不是一个完整的解决方案.
          • 在 Windows 上测试了 start 命令,它不会异步运行...您能否提供您从中获取该信息的来源?
          • @Alph.Dev 如果您使用的是 Windows,请查看我的回答:stackoverflow.com/a/40243588/1412157
          • @mynameis 您的回答准确地说明了启动命令不起作用的原因。这是因为/B 参数。我在这里解释过:stackoverflow.com/a/34612967/1709903
          【解决方案13】:

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

          【讨论】:

            猜你喜欢
            • 2011-03-21
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-01-04
            • 1970-01-01
            • 2010-12-20
            • 1970-01-01
            相关资源
            最近更新 更多