导读:本期聚焦于乐少创作的《PHP中执行带通配符的Shell命令时exec无法解析星号怎么办?》,敬请观看详情。在一个批量处理日志文件的 PHP 脚本里,有人发现 exec 执行列出命令时没有按预期匹配所有 .log 文件,星号被当成普通字符传递。这个现象通常不是 PHP 本身的缺陷,而是不同平台和调用方式对通配符展开的时机不同。Unix 环境下 exec 会经由 shell 执行命令字符串,通配符由 shell 负责展开;但在 Windows、数组参数模式或未经过 shell 的进程启动方式下,星号不会被解析。解决思路主要有两类:一是使用 PHP 原生 glob 或 scandir 自行枚举文件路径再拼接命令,二是显式调用 bash 或 cmd 开启通配符解析。本文结合具体代码示例,说明如何安全地执行带通配符的 Shell 命令,并避免命令注入风险。

在 PHP 里执行带通配符的 Shell 命令,本来是想利用星号匹配一批文件,但有时 exec 返回空数组或者报找不到文件,明显是星号没有按预期展开。要弄清楚这个问题,得先区分命令字符串是否真的交给了 shell 处理。PHP 的 exec、system、shell_exec 等函数虽然简单,但底层行为在 Unix 和 Windows 上并不完全相同,而且一旦调用方式改成数组参数,绕过了 shell,通配符自然就失效了。下面从展开机制开始,逐步给出可落地的解决办法。

PHP中执行带通配符的Shell命令时exec无法解析星号怎么办?

一、通配符为什么没有被展开

Unix 类系统下,用户在终端输入 ls *.log 时,并不是 ls 程序自己处理星号,而是登录 shell 先把 *.log 展开成 a.log、b.log 这样的参数列表,再交给 ls。PHP 的 exec、system、shell_exec 默认通过 /bin/sh -c 执行命令字符串,所以理论上通配符能够被 shell 展开。但实际中可能存在几个例外。

第一个例外是 Windows 环境。PHP 在 Windows 上通常调用 cmd.exe /c 来执行命令,cmd 的通配符展开规则比 bash 弱很多,并不是所有外部程序都会收到展开后的文件名。第二个例外是数组参数调用。虽然 exec 不接受数组,但如果项目里用了 proc_open 或 Symfony Process 的数组模式,参数会绕过 shell 直接传给可执行文件,这时星号就是普通字符。第三个例外是某些加固过的运行环境禁用了 shell,命令直接走 execve,自然也不会展开通配符。

所以当你发现 exec('ls *.txt', $output) 没有任何输出或提示错误时,不要急着改 PHP 配置,先确认当前平台和调用方式是否真正经过了 shell。

二、用 glob 在 PHP 侧展开文件列表

解决这个问题最稳妥的办法,是不要让 shell 去猜测通配符,而是在 PHP 代码里先把匹配到的文件找出来。PHP 内置的 glob 函数使用与 shell 类似的模式匹配规则,能返回符合条件的文件路径数组。这样做的好处是可控性强,可以在执行命令前对每个文件做校验、过滤或记录日志。

glob 支持星号、问号、方括号等通配符,还支持 GLOB_BRACE 等标志位。但需要注意,它返回的是文件路径,如果路径中包含空格或特殊字符,后续拼接命令时仍要处理转义。下面是一个批量压缩日志文件的例子。

$files = glob('/var/log/*.log');
foreach ($files as $file) {
    $command = sprintf('gzip %s', escapeshellarg($file));
    exec($command, $output, $returnCode);
    if ($returnCode !== 0) {
        echo '处理失败: ' . $file . PHP_EOL;
    }
}

这里把每个通过 glob 匹配到的文件路径交给 escapeshellarg 处理,再拼接到命令里,可以避免文件名中带有空格、分号等字符时被 shell 误解析。相比直接把星号丢给 exec,这个方案在 Windows 和 Unix 上行为一致,更符合“先确定文件、再执行操作”的思路。

三、强制通过 Shell 解析通配符

如果某些场景确实需要 shell 自己展开通配符,例如命令字符串来自配置文件且不想改动结构,那就可以显式指定解释器。Unix 下可以调用 /bin/bash -c,让 bash 来负责展开星号。不过要格外注意引号嵌套,否则容易引入命令注入风险。

下面是一个安全的写法,使用 escapeshellarg 把命令字符串整体转义后作为 bash -c 的参数传入。

$pattern = '*.log';
$innerCommand = 'ls ' . $pattern;
$command = '/bin/bash -c ' . escapeshellarg($innerCommand);
exec($command, $output, $returnCode);
print_r($output);

这段代码在 bash 子进程中执行 ls *.log,星号会被 bash 正常展开。由于 escapeshellarg 会把整个字符串包在单引号里,并转义内部的单引号,所以即使用户提供的模式里包含空格,也不会破坏外层命令结构。假如你直接用字符串拼接 /bin/bash -c 'ls *.log',虽然也能工作,但遇到用户输入时就容易出现漏洞。

Windows 下没有 bash 的完整语义,但可以利用 cmd 的 for 语句手动展开通配符。比如在 PHP 里执行 cmd /C "for %f in (*.log) do @echo %f",注意在批处理脚本中百分号需要写成 %%f,而在命令行直接执行时用 %f。通过 exec 调用时属于命令行场景,所以用单百分号。

for %f in (*.log) do @echo %f

这种写法的可读性较差,而且对包含空格的路径处理也不够友好,通常只适合在确实不能提前枚举文件的情况下使用。

四、Windows 与 Unix 路径处理的差异

在 Windows 上,文件路径使用反斜杠分隔,而 PHP 的单引号字符串会保留反斜杠。例如 'C:\Temp' 中的 \T 不会被当成转义序列,最终传给文件系统时仍是完整的 C:\Temp。不过拼接完整路径时需要注意,目录和文件名之间的反斜杠要写成 '\\' 或使用 DIRECTORY_SEPARATOR 常量。

如果后台程序不支持通配符,可以结合 scandir 和 fnmatch 完成匹配。下面的代码会列出 C:\Temp 下所有 .log 文件,再逐个调用压缩程序。

$dir = 'C:\Temp';
$files = scandir($dir);
foreach ($files as $file) {
    if (fnmatch('*.log', $file)) {
        $fullPath = $dir . '\\' . $file;
        exec('compressor.exe ' . escapeshellarg($fullPath), $output, $code);
        if ($code !== 0) {
            echo '处理失败: ' . $fullPath . PHP_EOL;
        }
    }
}

fnmatch 在 Windows 上同样可用,它会把星号当作通配符。使用前需要确认 PHP 版本已启用该函数,部分较旧的 Windows 构建可能需要开启 fnmatch 扩展。相比之下,glob 的方案更统一,因为它在两个平台上都直接返回路径数组。

五、安全加固与防注入

不管是让 shell 展开星号,还是用 PHP 自己匹配文件,最终执行命令时都绕不开字符串拼接。只要拼接的内容包含用户输入,就存在命令注入的可能性。比如一个允许用户传入模式的功能,如果直接写 exec("ls " . $_POST['pattern']),攻击者可以提交 ; rm -rf / 之类的负载。

推荐的防护策略分两层。第一层是对模式做白名单校验,只允许字母、数字、下划线、点、横线和星号、问号、方括号等通配符字符。第二层是对最终要放入命令的每个文件路径使用 escapeshellarg。下面是一个同时满足这两点的示例。

$userPattern = $_GET['pattern'] ?? '*.log';
if (!preg_match('/^[a-zA-Z0-9_.*?\[\]-]+$/', $userPattern)) {
    die('模式不合法');
}
$files = glob($userPattern);
foreach ($files as $file) {
    $command = sprintf('gzip %s', escapeshellarg($file));
    exec($command, $output, $returnCode);
    if ($returnCode !== 0) {
        echo '处理失败: ' . $file . PHP_EOL;
    }
}

这里正则里包含了方括号的通配符,需要转义才能正确表达字符类。如果嫌正则写起来麻烦,也可以直接对模式做字符黑名单过滤,但白名单始终更可靠。总之,只要不是完全可信的输入,就不要直接拼进 shell 命令。对于内部任务,尽量使用 glob 配合 escapeshellarg,这种方式不依赖 shell 展开,也容易审计。

PHP execShell通配符glob修改时间:2026-10-01 13:28:40

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/1001/64257.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。