2024 年 PHP 日本大会

shell_exec

(PHP 4, PHP 5, PHP 7, PHP 8)

shell_exec通过 shell 执行命令并将完整输出作为字符串返回

描述

shell_exec(字符串 $command): 字符串|false|null

此函数与反引号运算符相同。

注意:

在 Windows 上,底层管道以文本模式打开,这可能会导致该函数对二进制输出失败。对于此类情况,请考虑使用popen()

参数

command

将要执行的命令。

返回值

包含已执行命令输出的字符串,如果无法建立管道则返回false,如果发生错误或命令没有产生输出则返回null

注意:

此函数在发生错误或程序没有产生输出时都可能返回null。无法使用此函数检测执行失败。exec() 应该在需要访问程序退出代码时使用。

错误/异常

当无法建立管道时,会生成E_WARNING级别的错误。

示例

示例 #1 shell_exec() 示例

<?php
$output
= shell_exec('ls -lart');
echo
"<pre>$output</pre>";
?>

参见

添加注释

用户贡献的注释 35 条注释

243
trev at dedicate.co.uk
13 年前
如果您尝试在 shell_exec 中运行诸如“gunzip -t”之类的命令并得到空结果,则可能需要在命令末尾添加 2>&1,例如

并非总是有效
echo shell_exec("gunzip -c -t $path_to_backup_file");

应该有效
echo shell_exec("gunzip -c -t $path_to_backup_file 2>&1");

在上面的示例中,gunzip 输出开头的换行符似乎阻止了 shell_exec 打印其他任何内容。希望这能为其他人节省一两个小时。
50
alexandre dot schmidt at gmail dot com
8 年前
要在后台运行命令,必须将输出重定向到 /dev/null。这在 exec() 手册页中写明。不过,在某些情况下,您需要将输出记录到其他位置。将输出重定向到这样的文件对我来说不起作用

<?php
# 这不起作用!
shell_exec("my_script.sh 2>&1 >> /tmp/mylog &");
?>

使用上述命令仍然会挂起 Web 浏览器请求。

似乎您必须在命令行中添加“/dev/null”。例如,这有效

<?php
# 有效,但输出丢失了
shell_exec("my_script.sh 2>/dev/null >/dev/null &");
?>

但是我想要输出,所以我使用了这个

<?php
shell_exec
("my_script.sh 2>&1 | tee -a /tmp/mylog 2>/dev/null >/dev/null &");
?>

希望这对某人有所帮助。
3
mamedul.github.io
1 年前
使用 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";
}

}
1
shell master
1 年前
您可以使用单行代码打印 shell 脚本的输出,如下所示

<?= shell_exec("/proof.sh"); ?>
15
Rgemini
15 年前
在 Windows 下使用 shell-exec 时,处理捕获 stderr 输出的一个简单方法是在命令之前调用 ob_start(),之后调用 ob_end_clean(),如下所示

<?php
ob_start
()
$dir = shell_exec('dir B:');
if
is_null($dir)
{
// B: 不存在
// 在此处处理 stderr 输出
}
else
{
// B: 存在且 $dir 包含目录列表
// 在此处处理它
}
ob_end_clean(); // 删除证据 :-)
?>

如果 B: 不存在,则 $dir 将为 Null,并且输出缓冲区将捕获消息

“系统找不到指定的路径”。

(至少在 WinXP 下是这样)。如果 B: 存在,则 $dir 将包含目录列表,我们可能不需要关心输出缓冲区。无论如何,在继续之前都需要删除它。
5
smcbride at msn dot com
3 年前
从 PHP 7.4 开始,proc_open 可能是大多数用例的更好解决方案。它具有更好的控制和平台独立性。如果您仍然想使用 shell_exec(),我喜欢用一个允许更好控制的函数来包装它。

如下所示的内容解决了 Apache/PHP 上的一些后台进程问题。它还

public function sh_exec(string $cmd, string $outputfile = "", string $pidfile = "", bool $mergestderror = true, bool $bg = false) {
$fullcmd = $cmd;
if(strlen($outputfile) > 0) $fullcmd .= " >> " . $outputfile;
if($mergestderror) $fullcmd .= " 2>&1";
if($bg) {
$fullcmd = "nohup " . $fullcmd . " &";
if(strlen($pidfile)) $fullcmd .= " echo $! > " . $pidfile;
} else {
if(strlen($pidfile) > 0) $fullcmd .= "; echo $$ > " . $pidfile;
}
shell_exec($fullcmd);
}
6
eric dot peyremorte at iut-valence dot fr
17年前
我在使用shell_exec处理带重音字符时遇到了问题。

例如

从shell执行以下命令:

/usr/bin/smbclient '//BREZEME/peyremor' -c 'dir' -U 'peyremor%*********' -d 0 -W 'ADMINISTRATIF' -O 'TCP_NODELAY IPTOS_LOWDELAY SO_KEEPALIVE SO_RCVBUF=8192 SO_SNDBUF=8192' -b 1200 -N 2>&1

得到如下结果:

Vidéos D 0 Tue Jun 12 14:41:21 2007
Desktop DH 0 Mon Jun 18 17:41:36 2007

使用PHP代码:

shell_exec("/usr/bin/smbclient '//BREZEME/peyremor' -c 'dir' -U 'peyremor%*******' -d 0 -W 'ADMINISTRATIF' -O 'TCP_NODELAY IPTOS_LOWDELAY SO_KEEPALIVE SO_RCVBUF=8192 SO_SNDBUF=8192' -b 1200 -N 2>&1")

得到如下结果:

Vid Desktop DH 0 Mon Jun 18 17:41:36 2007

从出现重音字符的地方开始,两行输出合并了。

我找到了解决方案:PHP默认使用LOCALE=C执行命令。

我在shell_exec之前添加了以下几行代码,问题就解决了。

$locale = 'fr_FR.UTF-8';
setlocale(LC_ALL, $locale);
putenv('LC_ALL='.$locale);

只需根据你的语言环境进行调整。
1
哪个shell?
2年前
任何地方有没有提到shell_exec()运行的是哪个shell?在我的系统上,它运行的是sh而不是bash,这导致<()进程替换失败,并且没有错误消息。

文档不应该提到这个函数(和反引号 ``)运行哪个shell吗?
6
kamermans at teratechnologies dot net
17年前
我不确定这个函数会使用哪个shell,但是你可以这样查找:

<?php
$cmd
= 'set';
echo
"<pre>".shell_exec($cmd)."</pre>";
?>

在我的FreeBSD 6.1系统上,我得到以下结果:

USER=root
LD_LIBRARY_PATH=/usr/local/lib/apache2
HOME=/root
PS1='$ '
OPTIND=1
PS2='> '
LOGNAME=root
PPID=88057
PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin
SHELL=/bin/sh
IFS='
'

非常有趣。请注意,PATH可能并不像你需要的那么完整。我想通过ImageMagik的“convert”运行Ghostscript,结果不得不先添加我的路径才能运行命令。

<?php
$cmd
= 'export PATH="/usr/local/bin/"; convert -scale 25%x25% file1.pdf[0] file2.png 2>&1';
echo
"<pre>".shell_exec($cmd)."</pre>";
?>

另外,请注意,shell_exec()不会捕获STDERR,因此请使用“2>&1”将其重定向到STDOUT并捕获它。
3
RoBorg
17年前
Subversion错误“svn: Can't recode string”可能是由于区域设置错误造成的。请尝试
<?php
putenv
('LANG=en_US.UTF-8');
?>
(或你首选的区域设置)在调用shell_exec()之前。
2
pedroxam at gmail dot com
5年前
这是我的总结:

function execCommand($command, $log) {

if (substr(php_uname(), 0, 7) == "Windows")
{
//windows
pclose(popen("start /B " . $command . " 1> $log 2>&1", "r"));
}
else
{
//linux
shell_exec( $command . " 1> $log 2>&1" );
}

return false;
}
5
joelhy
15 年前
这是一个获取STDERR并丢弃STDOUT的简单方法:
在shell命令末尾添加 '2>&1 1> /dev/null'

例如:
<?php
$output
= shell_exec('ls file_not_exist 2>&1 1> /dev/null');
?>
3
jack dot harris-7ot2j4ip at yopmail dot Com
10年前
在Windows上,如果shell_exec没有返回你期望的结果,并且电脑位于企业网络上,请将Apache服务(或wampapache)设置为以你的帐户而不是“本地系统帐户”运行。你的帐户必须具有管理员权限。

要更改帐户,请转到控制台服务,右键单击Apache服务,选择属性,然后选择连接选项卡。
4
codeslinger at compsalot dot com
14年前
我花了很长时间才解决这个问题,所以我想在这里提一下。

如果你使用的是Eclipse,并且尝试执行以下操作:

<?php

$out
= shell_exec("php -s $File"); //这会失败
?>

在Eclipse调试器中运行时,它总是会失败。这在Linux和Windows上都会发生。我最终将问题隔离为Eclipse在调试时对环境进行的更改。

解决方法是强制ini设置。如果你不需要ini,则-n就足够了。

<?php

$out
= shell_exec("php -n -s $File"); //这可以工作
?>

当然,如果在调试器外部运行它,则无需-n也能正常工作。你可能希望使用调试标志来控制此行为。
2
tonysb at gmx dot net
22年前
shell_exec作为virtual()函数的替代品非常有用,尤其是在virtual()函数不可用时(例如Microsoft IIS)。你只需删除标题中发送的content type字符串即可。

<?
$mstrng = shell_exec('yourcgiscript.cgi');
$mstrng = ereg_replace( "Content-type: text/html", "", $mstrng );
echo $mstrng;
?>

这对我来说可以很好地替代SSI或virtual()函数。

Anton Babadjanov
http://www.vbcn.com.ar
2
saivert at saivert dot com
16年前
如何在Windows上获取驱动器的卷标

<?php

function GetVolumeLabel($drive) {
//尝试获取卷名
if (preg_match('#Volume in drive [a-zA-Z]* is (.*)\n#i', shell_exec('dir '.$drive.':'), $m)) {
$volname = ' ('.$m[1].')';
} else {
$volname = '';
}
return
$volname;
}

print
GetVolumeLabel("c");

?>

注意:正则表达式假设使用的是英文版本的Windows。对于不同语言版本的Windows,请相应地修改它。
3
匿名用户
19年前
小心如何提升PHP脚本的权限。谨慎规划是个好主意。很容易就会打开巨大的安全漏洞。以下是我从实验和Unix文档中收集到的一些有用的提示。

需要考虑的事项

1. 如果你在Unix中将php作为Apache模块运行,那么你运行的每个系统命令都将以apache用户身份运行。这是很合理的……Unix不允许以这种方式提升权限。如果你需要以提升的权限运行系统命令,请仔细考虑这个问题!

2. 如果你决定以root用户身份运行Apache,你绝对是疯了。你最好给自己一巴掌。总有更好的方法。

3. 如果你决定使用SUID,最好不要对脚本使用SUID。许多版本的Unix都禁用了脚本的SUID。SUID脚本会打开安全漏洞,所以即使这是一个选项,你也不总是想走这条路。编写一个简单的二进制文件,并将二进制文件的权限提升为SUID。在我看来,通过SUID传递系统命令(即让SUID接受命令名称作为参数)是一个糟糕的主意。你最好以root用户身份运行Apache!
2
rustleb at hotmail dot com
18年前
对于捕获stdout和stderr,当你不关心中间文件时,我使用以下方法获得了更好的结果:
<?php
function cmd_exec($cmd, &$stdout, &$stderr)
{
$outfile = tempnam(".", "cmd");
$errfile = tempnam(".", "cmd");
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("file", $outfile, "w"),
2 => array("file", $errfile, "w")
);
$proc = proc_open($cmd, $descriptorspec, $pipes);

if (!
is_resource($proc)) return 255;

fclose($pipes[0]); //实际上并不想提供任何输入

$exit = proc_close($proc);
$stdout = file($outfile);
$stderr = file($errfile);

unlink($outfile);
unlink($errfile);
return
$exit;
}
?>

这与重定向没有什么不同,只是它为你处理了临时文件(你可能需要将目录从“.”更改),并且由于proc_close调用而自动阻塞。这模拟了shell_exec的行为,并且可以获取stderr。
1
mcbeth at broggs dot org
21年前
关于最后一个例子的错误检查。许多shell都有&&运算符,所以你可以用它来连接你的命令,而不是用;。如果任何程序在任何时候失败,你将返回而不会运行其余部分。
0
Numabyte
3 年前
一个需要考虑的重要事项是,在某些情况下,例如在MacOS上,使用“2>&1”来正确输出exec()的字符串在将命令用括号括起来时效果很好。

例如:
使用括号括住命令
$cmd = "(mysqldump -h l ocalhost -u root -ppassword --databases login_practice_db > /Users/username/Desktop/testDB.sql) 2>&1";

$sys = shell_exec($cmd ); //有效并产生错误(localhost故意留有空格)

不使用括号括住命令
$cmd = "mysqldump -h l ocalhost -u root -ppassword --databases login_practice_db > /Users/username/Desktop/testDB.sql 2>&1";

$sys = shell_exec($cmd ); //什么也不做,变为空白,好像结果没有错误。空字符串。

shell命令执行如何细微到需要对字符串解析进行细致处理,这很有趣。
1
wally at soggysoftware dot co dot uk
12年前
正如其他人所指出的,如果执行的命令没有输出任何内容,则shell_exec和反引号运算符(`)都会返回NULL。

可以通过执行以下任何操作来解决此问题:

shell_exec ("silentcmd && echo ' '");

在这里,如果命令成功,我们只是输出空白空格——这解决了这个稍微奇怪的问题。从那里,你可以修剪()命令输出等。
3
jessop <AT> bigfoot <DOT> com
21年前
对于尝试在类Unix平台上使用shell_exec但似乎无法使其工作的人,只是一个快速的提醒。PHP作为系统上的Web用户执行(通常对于Apache来说是www),因此您需要确保Web用户对您尝试在shell_exec命令中使用的任何文件或目录具有权限。否则,它似乎不会做任何事情。
1
Martin Rampersad
20年前
我有PHP(CGI)和Apache。我还使用shell_exec() shell脚本,这些脚本使用PHP CLI。这种组合会破坏从调用返回的字符串值。我得到的是二进制垃圾。以#!/usr/bin/bash开头的shell脚本会正确返回其输出。

一个解决方案是强制一个干净的环境。PHP CLI不再有CGI环境变量来处理。

<?php

// 二进制垃圾。
$ExhibitA = shell_exec('/home/www/myscript');

// 完美。
$ExhibitB = shell_exec('env -i /home/www/myscript');

?>

-- 开始 /home/www/myscript
#!/usr/local/bin/phpcli
<?php

echo("Output.\n");

?>
-- 结束 /home/www/myscript
0
gabekon at gmail dot com
16年前
关于:kamermans 的注释,

在使用shell_exec时,我在PATH变量方面也遇到了类似的问题。即使使用硬编码的二进制文件的完整路径,我也收到关于找不到.so文件的错误。阅读一些资料后,我意识到我必须设置LD_LIBRARY_PATH变量。

<?php

$command
= 'export LD_LIBRARY_PATH="' .$path_to_library_dir .'"; ' .$path_to_binary;
shell_exec($command);

?>

希望这能省去一些人的麻烦,

- G
0
ruan at nospam dot tillcor dot com
18年前
按照Kenneth在此页面上提到的通过nanoweb服务器执行root脚本的方法,您很可能需要能够运行像lynx这样的文本模式浏览器并将php脚本传递给它(效果很好)。

在努力了一段时间后(lynx不断要求我下载文件而不是执行它),我意识到我必须另外安装php-cgi并将nanoweb配置文件修改为使用该php解释器而不是/usr/bin/php。(在Debian上,这是CLI版本)。

在Ubuntu 6.06上

apt-get install php5-cgi

编辑/etc/nanoweb/nanoweb.conf并快速重启Web服务器后,lynx和links将正确执行您的PHP脚本。

希望这对其他一些人有所帮助:)

Ruan Fourie
0
phillipberry at NOSPAM dot blisswebhosting dot com
19年前
我发现了一些奇怪的东西。

如果您运行exec,然后直接运行shell_exec,则shell_exec根本不会运行并将返回NULL。

为了使其工作,我在exec之后添加了一个sleep(5),现在shell_exec工作正常。
-1
smithnigelw at gmail dot com
6年前
在Windows上的PHP中,如果您收到“警告:shell_exec() [function.shell-exec]:无法执行”错误,则需要检查文件'C:\WINDOWS\system32\cmd.exe'的权限。您需要对此文件具有读取/执行权限。我建议使用sysinternals Process Monitor 'procmon.exe'来确认尝试运行'cmd.exe'的用户。按“进程名称”筛选为“php-cgi.exe”,而“路径”以“cmd.exe”结尾。查看具有访问被拒错误的任务的事件属性,它将显示“模拟”用户名。这通常是“Internet Guest Account”,通常是“NT AUTHORITY\IUSR”。
-2
Paul Cook
18年前
Nathan De Hert下面提到的技术相当不安全——您永远不应该将密码留在apache用户可读的文件中。

如果您需要在*nix系统上使用此类功能,请查看/etc/sudo文件(使用命令'visudo'编辑)。NOPASSWD标记允许指定的用户在无需指定密码的情况下以root用户身份运行指定的命令。这是一些额外的配置,但更安全。
-1
rigsbr at yahoo dot com dot br
18年前
如果您需要执行一个没有权限的命令,并且无法通过ssh执行它或安装任何扩展,那么在Apache 1.3.x和PHP 4中有一种方法。
在cgi-bin目录下创建一个文件,如下所示:

#!/usr/bin/php
<?
echo shell_exec('whoami');
?>

不要忘记设置您创建的文件的权限以执行它。因此,从浏览器调用它,您将看到此脚本将由shell用户执行,而不是nobody用户(如果运行PHP脚本,则为apache默认用户)。
-2
James McCormack
20年前
我有一个perl程序,它可以从命令行运行,但不能使用shell_exec()、exec()或system()——没有返回任何内容。我使用了完整的路径,并且权限设置正确。

事实证明,perl程序使用的内存比我的PHP.INI文件设置允许的更多。增加“memory_limit”解决了这个问题。
-3
dburles@NOSPAMgmailDOTcom
15 年前
在Windows中捕获错误输出的简单方法

// 我们将以php脚本为例
$out = shell_exec("php test.php 2> output");
print $out ? $out : join("", file("output"));

在这种情况下,我们假设如果脚本产生输出,则它已运行正常,则$out变量将包含输出,如果$out为空,则我们从名为“output”的文件中读取捕获的错误输出。

希望这对某些人有所帮助
-4
Ashraf Kaabi
18年前
我已经编写了一个完整的类,用于在后台运行、终止PID、检查是否正在运行。

<?php
/**
* @author Ashraf M Kaabi
* @name 高级Linux Exec
*/
class exec {
/**
* 在后台运行应用程序
*
* @param unknown_type $Command
* @param unknown_type $Priority
* @return PID
*/
function background($Command, $Priority = 0){
if(
$Priority)
$PID = shell_exec("nohup nice -n $Priority $Command > /dev/null & echo $!");
else
$PID = shell_exec("nohup $Command > /dev/null & echo $!");
return(
$PID);
}
/**
* 检查应用程序是否正在运行!
*
* @param unknown_type $PID
* @return boolen
*/
function is_running($PID){
exec("ps $PID", $ProcessState);
return(
count($ProcessState) >= 2);
}
/**
* 终止应用程序PID
*
* @param unknown_type $PID
* @return boolen
*/
function kill($PID){
if(
exec::is_running($PID)){
exec("kill -KILL $PID");
return
true;
}else return
false;
}
};
?>
-2
concept at conceptonline dot hu
19年前
有趣的是,如果您执行一个不在您的路径中的脚本(或者您输入错误,或者脚本根本不存在),您将不会获得返回值。错误将记录到Web服务器的error_log中。

有人可以添加一个说明如何(如果可以)覆盖此内容的注释,因为标准行为并非真正万无一失。
-3
php [AT] jsomers [DOT] be
19年前
<?php

/**
* PHP 终止进程
*
* 有时,脚本可能会持续运行而本不应该,即使我们关闭浏览器或关机也无法停止。因为并不总是很容易使用 SSH,所以这里提供一种变通方法。
*
* @author Jensen Somers <[email protected]>
* @version 1.0
*/

class KillAllProcesses {
/**
* 构造函数
*/
function killallprocesses() {
$this->listItems();
}

/**
* 列出所有项目
*/
function listItems() {
/*
* PS Unix 命令用于报告进程状态
* -x 选择没有控制 tty 的进程
*
* 输出如下所示:
* 16479 pts/13 S 0:00 -bash
* 21944 pts/13 R 0:00 ps -x
*
*/
$output = shell_exec('ps -x');

$this->output($output);

// 将每一行放入数组中
$array = explode("\n", $output);

$this->doKill($array);
}

/**
* 打印进程列表
* @param string $output
*/
function output($output) {
print
"<pre>".$output."</pre>";
}

/**
* 终止所有进程
* 应该可以进行过滤,但我现在不做。
* @param array $array
*/
function doKill($array) {
/*
* 因为 $output 的第一行看起来像这样
* PID TTY STAT TIME COMMAND
* 所以我们将跳过这一行。
*/
for ($i = 1; $i < count($array); $i++) {
$id = substr($array[$i], 0, strpos($array[$i], ' '));
shell_exec('kill '.$id);
}
}
}

new
KillAllProcesses();

?>

这不是最好的解决方案,但我曾在需要快速解决问题时使用过几次。
请注意,我终止了所有进程。在我的服务器上,ps -x 只会返回大约 4 次 /sbin/apache,终止它们非常安全,不会出现任何问题。
-3
martin at intelli-gens dot com
14年前
另外,在费了很大的劲儿之后,我终于发现为什么 shell_exec 对我来说不起作用了,在我的情况下,某些设置需要配置(通常这些设置是默认配置的)。

选项 -jo
-j 表示:不要重新创建存档中找到的路径。
-o 表示:始终覆盖文件。

我还需要指定目标路径(即使未指定时应该在同一目录中解压),这可以通过 -d [path] 来完成。

奇怪的是,在命令行中使用该命令时,我不需要添加这些选项,而只在使用 shell_exec 调用时才需要。

所以,在我这里,完整的 PHP 命令如下所示:

shell_exec('unzip -jo /path_to_archive/archive.zip -d /destination_path')

在前面添加一个 echo 将会非常有帮助。
它应该会显示类似这样的信息:


Archive: /path_to_zip/archive.zip
inflating: /destination_path/file1.jpeg
inflating: /destination_path/file2.jpeg
inflating: /destination_path/file3.jpeg
To Top