这将在后台执行 $cmd(没有 cmd 窗口),而无需 PHP 等待其完成,无论是在 Windows 还是 Unix 上。
<?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 — 执行外部程序
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 "返回状态 $retval 和输出:\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。
这将在后台执行 $cmd(没有 cmd 窗口),而无需 PHP 等待其完成,无论是在 Windows 还是 Unix 上。
<?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 "进程当前正在运行";
}else{
echo "进程未运行。";
}
?>
<?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 中执行,只会打印到终端;如果在浏览器中执行,则会丢失。
我强烈建议将这些说明添加到主描述中,而不是将其遗漏在底部的注释中。
无法将 exec 命令的输出显示在 $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数组变量,然后从exec函数返回的数组中获取每个数组成员。如果您要传递多个数组,或者需要跟踪数组键和值,那么在perl脚本末尾打印每个数组或哈希变量时,应使用下划线将值与键和数组名称连接起来,例如
foreach (@array) print "(array name)_(member_key)_($_)" ;
然后,您只需遍历exec函数返回的数组,并沿下划线分割每个变量。
在此,我特别感谢Marat提供的知识。希望这对其他寻找类似答案的人有用!
如果您想在后台运行命令并使用建议的escapeshellcmd(),请确保在括号外进行重定向!这些都是您希望shell解释的内容。
例如 exec(escapeshellcmd('mycommand and arguments').' > /dev/null &');
我试图通过执行cacls并在Windows环境(使用Apache)的php中解析它来获取远程计算机的访问列表。首先,我发现了Windows SysInternals的psexec.exe。
但是使用以下代码行,我什么也没得到,它卡住了,尽管从命令行它工作得很好
<?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(未找到命令)结果代码。原因是,shell(/bin/sh)在chroot中缺失,但是exec()命令需要shell。
一个快速简便的解决方案是将/bin/sh(它是静态链接的)复制到chroot目录
<?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的注释:以下是该注释贡献者建议将其放在名为'pstools.inc.php'的文件中以执行进程、检查进程是否存在以及按ID终止进程的Linux脚本。灵感来自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分别是标准输入、标准输出和标准错误。