ChatGPT解决这个技术问题 Extra ChatGPT

PHP中的异步shell exec

我有一个 PHP 脚本,它需要调用一个 shell 脚本,但根本不关心输出。 shell 脚本进行了许多 SOAP 调用并且完成速度很慢,所以我不想在 PHP 请求等待回复时减慢它的速度。事实上,PHP 请求应该能够在不终止 shell 进程的情况下退出。

我研究了各种 exec()shell_exec()pcntl_fork() 等函数,但它们似乎都没有提供我想要的东西。 (或者,如果他们这样做,我不清楚怎么做。)有什么建议吗?

无论您选择哪种解决方案,您还应该考虑使用 niceionice 来防止 shell 脚本使您的系统不堪重负(例如 /usr/bin/ionice -c3 /usr/bin/nice -n19

C
Community

如果它“不关心输出”,难道不能用 & 调用脚本的 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 时,后台脚本将被杀死……只要在很长的工作中注意这一点,或者如果你在时间上不走运,你想知道为什么你的工作消失了……
M
MartyIX

我为此使用了 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" '); 如果 command 包含引号,请参阅 {1 } 省去你的头疼
仔细想想,让 sudo 运行 sh 并不是最好的主意,因为它基本上为 sudo 提供了对所有内容的 root 访问权限。我恢复使用 echo "sudo command" | at now 并在 /etc/at.deny 中注释掉 www-data
@Julien 也许您可以使用 sudo 命令制作一个 shell 脚本并启动它。只要您没有将任何用户提交的值传递给所述脚本
at 包(和命令)对于某些 linux 发行版来说是非默认的。它还需要运行 atd 服务。由于缺乏解释,该解决方案可能难以理解。
L
LucaM

致所有 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);
D
Darryl Hein

在 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 调用将其输出到日志中,而你的解决方案是唯一满足所有条件的解决方案。
C
Community

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


L
Leo

在 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
佚名

正确的方法(!)做到这一点是

fork() setid() execve()

fork 分叉,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 已经尝试过)。
p
philfreo

我用这个...

/** 
 * 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


A
Anton Pelykh

我还发现 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_*内部函数。
G
Gordon Forsythe

我发现真正对我有用的唯一方法是:

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
f
felipe.zkn

您还可以将 PHP 脚本作为 daemoncronjob 运行:#!/usr/bin/php -q


g
geocar

使用命名的 fifo。

#!/bin/sh
mkfifo trigger
while true; do
    read < trigger
    long_running_task
done

然后,每当您想启动长时间运行的任务时,只需编写一个换行符(非阻塞到触发器文件。

只要您的输入小于 PIPE_BUF 并且它是单个 write() 操作,您就可以将参数写入 fifo 并让它们在脚本中显示为 $REPLY


L
LF00

不使用队列,您可以像这样使用 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);