这将在 Windows 和 Unix 上在后台(没有 cmd 窗口)执行 $cmd,而不会让 PHP 等待它完成。
<?php
function execInBackground($cmd) {
if (substr(php_uname(), 0, 7) == "Windows"){
pclose(popen("start /B ". $cmd, "r"));
}
else {
exec($cmd . " > /dev/null &");
}
}
?>
(PHP 4, PHP 5, PHP 7, PHP 8)
exec — 执行外部程序
exec() 执行给定的 command
。
command
将要执行的命令。
output
如果存在 output
参数,则指定数组将填充来自命令的每一行输出。 结尾空格,例如 \n
,不包含在此数组中。 注意,如果数组已经包含一些元素,exec() 将追加到数组的末尾。 如果您不希望函数追加元素,请在将数组传递给 exec() 之前调用 unset()。
result_code
如果 result_code
参数与 output
参数一起存在,则执行的命令的返回状态将写入此变量。
来自命令结果的最后一行。 如果您需要执行一个命令并直接将命令的所有数据传回,没有任何干扰,请使用 passthru() 函数。
如果失败,返回 false
。
要获取执行的命令的输出,请务必设置并使用 output
参数。
版本 | 描述 |
---|---|
8.0.0 | 如果 command 为空或包含空字节,exec() 现在将抛出 ValueError。 以前它会发出 E_WARNING 并返回 false 。 |
示例 #1 一个 exec() 示例
<?php
// 输出拥有运行 php/httpd 进程的用户名
// (在具有路径中的 "whoami" 可执行文件的系统上)
$output=null;
$retval=null;
exec('whoami', $output, $retval);
echo "Returned with status $retval and output:\n";
print_r($output);
?>
上面的例子将输出类似于以下内容
Returned with status 0 and output: Array ( [0] => cmb )
当允许用户提供的数据传递给此函数时,请使用 escapeshellarg() 或 escapeshellcmd() 来确保用户无法欺骗系统执行任意命令。
注意:
如果使用此函数启动程序,为了使其在后台继续运行,程序的输出必须重定向到文件或其他输出流。 否则,PHP 将挂起,直到程序执行结束。
注意:
在 Windows 上,exec() 将首先启动 cmd.exe 来启动命令。 如果您想在不启动 cmd.exe 的情况下启动外部程序,请使用 proc_open(),并将
bypass_shell
选项设置为 true。
这将在 Windows 和 Unix 上在后台(没有 cmd 窗口)执行 $cmd,而不会让 PHP 等待它完成。
<?php
function execInBackground($cmd) {
if (substr(php_uname(), 0, 7) == "Windows"){
pclose(popen("start /B ". $cmd, "r"));
}
else {
exec($cmd . " > /dev/null &");
}
}
?>
(仅适用于 Linux 用户)。
我们现在知道如何在 Linux 中使用 & 运算符派生进程。
并使用命令:nohup MY_COMMAND > /dev/null 2>&1 & echo $!,我们可以返回进程的 PID。
这个小类是为了让您可以跟踪创建的进程(意味着启动/停止/状态)。
您可以使用它来启动一个进程或加入一个现有的 PID 进程。
<?php
// 您可以使用 status()、start() 和 stop()。 注意 start() 方法自动调用一次。
$process = new Process('ls -al');
// 或者,如果您获得了 PID,但只有 status() 方法将起作用。
$process = new Process();
$process.setPid(my_pid);
?>
<?php
// 然后您可以启动/停止/检查作业的状态。
$process.stop();
$process.start();
if ($process.status()){
echo "The process is currently running";
}else{
echo "The process is not running.";
}
?>
<?php
/* 一种简单的跟踪外部进程的方法。
* 您是否曾经想在 PHP 中执行一个进程,但仍然希望对该进程有一定的控制?嗯.. 这是实现的一种方法。
* @兼容性: 仅限 Linux。(Windows 不起作用)。
* @作者: Peec
*/
class Process{
private $pid;
private $command;
public function __construct($cl=false){
if ($cl != false){
$this->command = $cl;
$this->runCom();
}
}
private function runCom(){
$command = 'nohup '.$this->command.' > /dev/null 2>&1 & echo $!';
exec($command ,$op);
$this->pid = (int)$op[0];
}
public function setPid($pid){
$this->pid = $pid;
}
public function getPid(){
return $this->pid;
}
public function status(){
$command = 'ps -p '.$this->pid;
exec($command,$op);
if (!isset($op[1]))return false;
else return true;
}
public function start(){
if ($this->command != '')$this->runCom();
else return true;
}
public function stop(){
$command = 'kill '.$this->pid;
exec($command);
if ($this->status() == false)return true;
else return false;
}
}
?>
请注意,EXEC 会忽略 stderr!
所以类似
mkdir /impossible path
将失败,并且什么也不显示,您必须使用
mkdir /impossible 2>&1
才能正确捕获错误消息
否则
如果在 CLI 中执行,只会打印到终端,如果在浏览器中执行,就会丢失。
我强烈建议将这些注释添加到主要描述中,而不是丢失在底部的注释中。
无法从您执行的命令中获取输出并显示在 $output 数组中吗?
它是否正在您的 shell 中回显所有内容?
在您命令的末尾追加“2>&1”,例如
exec("xmllint --noout ~/desktop/test.xml 2>&1", $retArr, $retVal);
将使用预期输出填充数组 $retArr;每行对应一个数组键。
我也曾苦苦挣扎着让程序在 Windows 后台运行,而脚本继续执行。与其他解决方案不同,这种方法允许您以最小化、最大化或完全没有窗口的方式启动任何程序。llbra@phpbrasil 的解决方案确实有效,但它有时会在您真正想隐藏任务时在桌面上产生一个不必要的窗口。
在后台以最小化方式启动 Notepad.exe
<?php
$WshShell = new COM("WScript.Shell");
$oExec = $WshShell->Run("notepad.exe", 7, false);
?>
在后台以不可见的方式启动 shell 命令
<?php
$WshShell = new COM("WScript.Shell");
$oExec = $WshShell->Run("cmd /C dir /S %windir%", 0, false);
?>
以最大化方式启动 MSPaint,并在您关闭它之前继续执行脚本
<?php
$WshShell = new COM("WScript.Shell");
$oExec = $WshShell->Run("mspaint.exe", 3, true);
?>
有关 Run() 方法的更多信息,请访问
http://msdn.microsoft.com/library/en-us/script56/html/wsMthRun.asp
如果您尝试在使用信号 SIGCHLD 的脚本中使用 exec(即 pcntl_signal(SIGCHLD,'sigHandler');),它将返回 -1 作为命令的退出代码(尽管输出是正确的!)。要解决此问题,请删除信号处理程序,并在 exec 之后再次添加它。代码将类似于以下内容
...
pcntl_signal(SIGCHLD, 'sigHandler');
...
...
(更多代码、函数、类等)
...
...
// 现在通过 exec 执行命令
// 清除信号
pcntl_signal(SIGCHLD, SIG_DFL);
// 执行命令
exec('mycommand',$output,$retval);
// 将信号设置回我们的处理程序
pcntl_signal(SIGCHLD, 'sigHandler');
// 在此阶段,我们有 $retval 的正确值。
相同的解决方案也适用于 system 和 passthru。
我尝试在 Windows 后台执行命令。
在花了数小时尝试了所有这些半成品示例后,我想分享我发现的有效语法(至少在 Windows 中如此)。这在 Linux 下没有测试,因为有更优雅的方法来生成进程。
基于 Arno van den Brink 的函数。
<?php
function execInBackground($cmd) {
if (substr(php_uname(), 0, 7) == "Windows"){
pclose(popen("start /B ". $cmd, "r"));
}
else {
exec($cmd . " > /dev/null &");
}
}
?>
这与以下示例完美配合:
<?php
execInBackground('del c:\tmp\*.*')
?>
但以下操作无效
<?php
execInBackground('\"c:\path with spaces\my program.exe\"')
?>
为什么?
当 Windows 看到引号(")时,它认为这是窗口标题,而不是命令。
因此,当您的命令需要引号时,您必须先提供一个窗口名称,例如
execInBackground("\"title\"" "\"c:\path with spaces\my program.exe\")
窗口标题必须使用引号。否则 Windows 会认为这是程序名称。
很奇怪,但“嘿!这是 Windows!”:)
exec 会从命令的输出中去除尾随空白字符。这使得无法捕获重要的空白字符。例如,假设一个程序输出制表符分隔的文本列,最后一列在某些行上包含空字段。尾随制表符很重要,但会被去除。
如果您需要保留尾随空白字符,则必须使用 popen()。
在 Windows-Apache-PHP 服务器上,同时多次使用 exec 命令会出现问题。如果同一个用户同时多次加载包含 exec 命令的脚本,服务器将会冻结。
在我的例子中,使用 exec 命令的 PHP 脚本用作图像标签的来源。在一个 HTML 中使用多个图像会导致服务器停止。
此问题在此处 (http://bugs.php.net/bug.php?id=44942) 有描述,以及解决方案 - 在执行 exec 命令之前停止会话,并在执行完毕后重新启动会话。
<?php
session_write_close();
exec($cmd);
session_start();
?>
在 Unix 上,要将命令 $cmd 在后台执行,唯一允许的标准输出重定向语法是“> /path/to/file &”。任何其他有效的标准输出重定向语法都无法让命令在后台运行。
<?php
// 以下代码将在后台运行
exec($cmd . " > /path/to/file &");
// 以下代码将不会在后台运行
exec($cmd . " >> /path/to/file &");
exec($cmd . " &> /path/to/file &");
exec($cmd . " &>> /path/to/file &");
exec($cmd . " 2>&1 > /path/to/file &");
exec($cmd . " 2>&1 >> /path/to/file &");
?>
据我所知,没有办法使用 exec 函数将 Perl 数组传递回 PHP 脚本。
建议是在 Perl 脚本的末尾打印 Perl 数组变量,然后从 exec 函数返回的数组中获取每个数组成员。如果要传递多个数组,或者需要跟踪数组键和值,那么在 Perl 脚本的末尾打印每个数组或哈希变量时,应将值与键和数组名称连接起来,使用下划线,例如
foreach (@array) print "(array name)_(member_key)_($_)" ;
然后只需遍历 exec 函数返回的数组,并根据下划线分割每个变量。
在此,我特别感谢 Marat 的知识。希望这对其他寻找类似答案的人有所帮助!
如果您想在后台运行命令,并使用 escapeshellcmd() 的建议,请确保您将重定向放在括号之外!这些是您希望 shell 解释的内容。
例如:exec(escapeshellcmd('mycommand and arguments').' > /dev/null &');
我试图通过执行 cacls 并使用 PHP 在 Windows 环境下的 Apache 中对其进行解析,从远程计算机获取访问列表。
但是使用以下代码行,我没有得到任何内容,它被挂起了,虽然从命令行它运行得很好
<?php exec ('c:\\WINDOWS\\system32\\psexec.exe \\192.168.1.224 -u myuser -p mypassword -accepteula cacls c:\\documents\\RRHH && exit', $arrACL ); ?>
为了使其正常工作,我只需执行以下步骤
- 执行 services.msc 并找到 Apache 服务(在我的情况下是 wampapache)
- 右键单击 > 登录选项卡,并将本地系统帐户更改为创建的帐户,输入用户名和密码,然后重新启动服务。
(我将此用户添加到管理员组以避免权限问题,但这是不推荐的...)
它起作用了!它也可能适用于 IIS,所以如果您遇到相同的问题,请尝试一下....
希望这对某些人有所帮助,并对我的英语表示歉意
使用 PHP 执行命令的跨函数解决方案-
function php_exec( $cmd ){
if( function_exists('exec') ){
$output = array();
$return_var = 0;
exec($cmd, $output, $return_var);
return implode( " ", array_values($output) );
}else if( function_exists('shell_exec') ){
return shell_exec($cmd);
}else if( function_exists('system') ){
$return_var = 0;
return system($cmd, $return_var);
}else if( function_exists('passthru') ){
$return_var = 0;
ob_start();
passthru($cmd, $return_var);
$output = ob_get_contents();
ob_end_clean(); // 使用此方法代替 ob_flush()
return $output;
}else if( function_exists('proc_open') ){
$proc=proc_open($cmd,
array(
array("pipe","r"),
array("pipe","w"),
array("pipe","w")
),
$pipes);
return stream_get_contents($pipes[1]);
}else{
return "@PHP_COMMAND_NOT_SUPPORT";
}
}
<此注释从系统页面复制>
这是针对 WINDOWS 用户。我正在运行 Apache,我已经尝试了几个小时,但仍然无法捕获命令的输出。
我尝试了这里写的所有方法,然后继续在线搜索,但仍然没有结果。命令的输出从未被捕获。我得到的只是一个空数组。
最后,我在某个神奇家伙的博客评论中找到了解决方案。
将字符串 ' 2>&1' 添加到命令名称中,终于返回了输出!由于它使用流重定向将输出重定向到正确的位置,因此此方法适用于 PHP 中的 exec() 和 system()!
<?php
exec("yourCommandName 2>&1", $output);
var_dump($output);
?>
花了相当长的时间才弄清楚我接下来要发布的代码行。如果您想在后台执行命令,而不让脚本等待结果,您可以执行以下操作
<?php
passthru("/usr/bin/php /path/to/script.php ".$argv_parameter." >> /path/to/log_file.log 2>&1 &");
?>
这里有一些重要的事情。
首先:将 PHP 二进制文件的完整路径放在其中,因为此命令将在 Apache 用户下运行,并且您可能没有在该用户中设置 php 这样的命令别名。
第二:注意命令字符串末尾的两个内容:'2>&1' 和 '&'。'2>&1' 用于将错误重定向到标准 IO。最重要的是命令字符串末尾的 '&',它告诉终端不要等待响应。
第三:确保您对 'log_file.log' 文件具有 777 权限
尽情享受!
针对 OpenBSD 用户的说明
在 OpenBSD 的默认配置中,PHP 运行在一个 chroot 中。因此 exec() 命令将无法正常工作。您将收到一个 127(命令未找到)的结果代码。原因是 chroot 中缺少 shell(/bin/sh),但 exec() 命令需要 shell。
一个快速而肮脏的解决方案是在 chroot 目录中复制 /bin/sh(它被静态链接)
<?php
$ cp /bin/sh /var/www/bin
?>
(我在 OpenBSD 7.0 上使用 PHP 8.0.11 发现了这个问题。)
在 Windows 中,exec() 会发出对 "cmd /c your_command" 的内部调用。这意味着您的命令必须遵循 cmd.exe 强制执行的规则,包括在完整命令周围添加一组额外的引号
- http://ss64.com/nt/cmd.html
当前的 PHP 版本会考虑到这一点,并自动添加引号,但旧版本不会。
显然,该更改是在 PHP/5.3.0 中完成的,但没有向 5.2.x 反向移植,因为它是一个向后不兼容的更改。总结一下
- 在 PHP/5.2 和更早版本中,您必须将完整命令加参数用双引号括起来
- 在 PHP/5.3 和更高版本中,您不需要这样做(如果您这样做,您的脚本将崩溃)
如果您对内部机制感兴趣,以下是源代码
sprintf(cmd, "%s /c \"%s\"", TWG(comspec), command);
可以在 http://svn.php.net/viewvc/ 中找到它(请找到 php/php-src/trunk/TSRM/tsrm_win32.c,注释系统不允许直接链接)。
[danbrown AT php DOT net 补充说明:以下是一个 Linux 脚本,该脚本由本注释的贡献者建议放在名为 'pstools.inc.php' 的文件中,用于执行进程,检查进程是否存在,并通过 ID 杀死进程。灵感来自 https://php.net/exec#59428 中的 Windows 版本。]
<?php
function PsExecute($command, $timeout = 60, $sleep = 2) {
// 首先,执行进程,获取进程 ID
$pid = PsExec($command);
if( $pid === false )
return false;
$cur = 0;
// 其次,循环 $timeout 秒,检查进程是否正在运行
while( $cur < $timeout ) {
sleep($sleep);
$cur += $sleep;
// 如果进程不再运行,则返回 true;
echo "\n ---- $cur ------ \n";
if( !PsExists($pid) )
return true; // 进程必须已退出,成功!
}
// 如果进程在超时后仍在运行,则杀死进程并返回 false
PsKill($pid);
return false;
}
function PsExec($commandJob) {
$command = $commandJob.' > /dev/null 2>&1 & echo $!';
exec($command ,$op);
$pid = (int)$op[0];
if($pid!="") return $pid;
return false;
}
function PsExists($pid) {
exec("ps ax | grep $pid 2>&1", $output);
while( list(,$row) = each($output) ) {
$row_array = explode(" ", $row);
$check_pid = $row_array[0];
if($pid == $check_pid) {
return true;
}
}
return false;
}
function PsKill($pid) {
exec("kill -9 $pid", $output);
}
?>
如果 SAFE_MODE 处于开启状态,并且您尝试通过在命令行末尾添加 "> /dev/null 2> /dev/null & echo $!" 来在后台运行脚本,则浏览器将一直挂起,直到脚本完成。
我的解决方案
创建一个 shell 脚本(例如 runscript.sh),其中包含您尝试在后台运行的脚本的执行行。
runscript.sh 通过一个不带重定向字符串的 exec() 调用运行,重定向字符串现在位于 runscript.sh 中。
runscript.sh 将几乎立即返回,因为原始脚本的输出被重定向,因此不会挂起您的浏览器,并且脚本在后台正常运行。
这是第二次遇到这个问题,我认为其他人可能也会发现这个笔记有用。
我正在创建一个长时间运行的 exec'd 进程,我可以使用页面提交在 2 小时后访问它。问题是,当我第一次访问页面时,一切按预期工作。第二次,网页浏览器一直等待,但没有收到任何消息——CPU 时间没有受到影响,因此很明显有什么东西被阻塞了。
实际发生的情况是,所有打开的文件都被复制到 exec'd 进程中,包括网络连接。因此,当我第二次尝试访问网页时,我正在被给予旧的 http 网络连接,现在它被忽略了。
解决方案是扫描从 3 开始的所有文件句柄,并将它们全部关闭。请记住,句柄 0、1 和 2 分别是标准输入、标准输出和标准错误。