远程执行带参数的PHP脚本时目录路径异常问题求助
我来帮你拆解这个问题——核心问题大概率是远程执行环境的工作目录(CWD)不符合预期,或者是执行用户权限不足导致路径访问失败。本地执行正常是因为你在脚本所在目录操作,相对路径解析正确;但远程调用时,PHP进程的工作目录可能是/root、/var/www/html这类默认路径,而且执行用户(比如web服务器的www-data)可能没有目标目录的写入权限,自然就会出问题。下面针对你提到的5种远程执行方式逐一给出修正方案:
1. shell_exec/exec 方式
这两个函数的问题本质是执行命令时的工作目录不对,而且脚本里的相对路径会基于这个CWD解析。解决方法有两种:
切换工作目录后执行:在命令前加上
cd切换到脚本所在目录,再执行脚本:// remote_go.php $dirParam = '1232'; // 替换成go.php所在的绝对路径 $scriptDir = '/path/to/your/go_script_directory'; $command = "cd {$scriptDir} && php go.php {$dirParam}"; $output = shell_exec($command); // 或者用exec捕获错误与返回码 // exec($command, $outputArray, $returnCode);直接使用脚本的绝对路径:不需要切换目录,直接指定go.php的绝对路径执行:
$command = "php /path/to/your/go_script_directory/go.php 1232"; $output = shell_exec($command);
同时,一定要修改go.php里的路径为绝对路径,避免依赖工作目录:
// go.php $targetDir = '/www/client/' . $argv[1]; // 用__DIR__获取当前脚本所在目录,拼接template路径 $templateDir = __DIR__ . '/template'; // 递归复制目录的逻辑 function copyDir($source, $dest) { if (!is_dir($dest)) mkdir($dest, 0755, true); $files = scandir($source); foreach ($files as $file) { if ($file == '.' || $file == '..') continue; is_dir("$source/$file") ? copyDir("$source/$file", "$dest/$file") : copy("$source/$file", "$dest/$file"); } } copyDir($templateDir, $targetDir); // 原来的移动操作可以省略,直接复制到目标路径即可
2. ssh2 扩展方式
ssh2_exec执行命令时,同样要处理工作目录和权限问题。修改后的代码如下:
// remote_go.php $host = 'your_remote_host'; $user = 'ssh_username'; $pass = 'ssh_password'; $scriptPath = '/path/to/your/go_script_directory/go.php'; $dirParam = '1232'; $connection = ssh2_connect($host, 22); if (!ssh2_auth_password($connection, $user, $pass)) { die('SSH登录失败'); } // 方式一:切换目录执行 $command = "cd " . dirname($scriptPath) . " && php go.php {$dirParam}"; // 方式二:绝对路径执行 // $command = "php {$scriptPath} {$dirParam}"; $stream = ssh2_exec($connection, $command); // 同时捕获错误输出 $errorStream = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR); stream_set_blocking($stream, true); stream_set_blocking($errorStream, true); $output = stream_get_contents($stream); $error = stream_get_contents($errorStream); fclose($stream); fclose($errorStream); echo "执行输出:{$output}\n错误信息:{$error}";
注意:SSH登录的用户需要对/www/client目录有写入权限,必要时可以在命令前加sudo(需提前配置免密sudo,避免交互)。
3. phpseclib(SSH2类库)方式
phpseclib的使用逻辑和ssh2扩展类似,核心还是指定正确的路径和捕获错误:
// remote_go.php require 'vendor/autoload.php'; use phpseclib3\Net\SSH2; $ssh = new SSH2('your_remote_host'); if (!$ssh->login('ssh_username', 'ssh_password')) { exit('SSH登录失败'); } $scriptDir = '/path/to/your/go_script_directory'; $dirParam = '1232'; // 切换目录执行命令 $output = $ssh->exec("cd {$scriptDir} && php go.php {$dirParam}"); // 获取错误信息 $error = $ssh->getStderr(); echo "输出:{$output}\n错误:{$error}";
同样,确保go.php里的路径都是绝对路径,且SSH用户有目标目录的写入权限。
4. 基于cURL的HTTP触发方式
把go.php改成可通过HTTP请求触发的接口(需做好安全校验),这种方式适合无法直接执行SSH命令的场景:
首先修改go.php:
// go.php // 安全校验:只允许指定密钥访问 $allowedKey = 'your_secret_key'; if (!isset($_GET['key']) || $_GET['key'] !== $allowedKey) { die('无权限访问'); } if (!isset($_GET['dir']) || preg_match('/[^0-9a-zA-Z-_]/', $_GET['dir'])) { die('无效的目录名参数'); } $dirName = $_GET['dir']; $templateDir = __DIR__ . '/template'; $targetDir = '/www/client/' . $dirName; // 递归复制目录(同之前的copyDir函数) function copyDir($source, $dest) { if (!is_dir($dest)) mkdir($dest, 0755, true); $files = scandir($source); foreach ($files as $file) { if ($file == '.' || $file == '..') continue; is_dir("$source/$file") ? copyDir("$source/$file", "$dest/$file") : copy("$source/$file", "$dest/$file"); } } copyDir($templateDir, $targetDir); echo "目录 {$dirName} 已成功创建";
然后用cURL远程调用:
// remote_go.php $ch = curl_init('http://your_remote_host/go.php?key=your_secret_key&dir=1232'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 如果是HTTPS,生产环境建议保留证书校验,测试环境可临时关闭 // curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); echo "HTTP状态码:{$httpCode}\n响应:{$response}";
注意:web服务器的运行用户(比如www-data)需要对/www/client有写入权限,否则会出现权限错误。
5. 基于proc_open的SSH执行方式
proc_open可以更灵活地控制命令执行的输入输出,适合需要处理交互的场景(比如免密SSH登录):
// remote_go.php $descriptorspec = [ 0 => ["pipe", "r"], // 标准输入 1 => ["pipe", "w"], // 标准输出 2 => ["pipe", "w"] // 错误输出 ]; $sshUser = 'ssh_username'; $host = 'your_remote_host'; $scriptPath = '/path/to/your/go_script_directory/go.php'; $dirParam = '1232'; // 假设已配置本地服务器免密登录远程服务器 $command = "ssh {$sshUser}@{$host} 'cd " . dirname($scriptPath) . " && php go.php {$dirParam}'"; $process = proc_open($command, $descriptorspec, $pipes); if (is_resource($process)) { $stdout = stream_get_contents($pipes[1]); $stderr = stream_get_contents($pipes[2]); // 关闭管道 fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]); // 获取执行返回码 $returnCode = proc_close($process); echo "返回码:{$returnCode}\n输出:{$stdout}\n错误:{$stderr}"; } else { die('无法启动进程'); }
这种方式需要提前配置SSH密钥对,避免执行命令时需要输入密码。
通用排查要点
不管用哪种方式,都要注意这几点:
- 绝对路径优先:所有涉及文件/目录的操作都用绝对路径,避免依赖工作目录。用
__DIR__获取脚本所在目录是最可靠的方式。 - 检查工作目录:可以在远程命令中加入
pwd查看当前工作目录,比如ssh user@host "pwd && php go.php 1232",确认CWD是否符合预期。 - 权限排查:用
ls -ld /www/client查看目录权限,确认执行用户有写入权限。必要时用chown www-data:www-data /www/client或chmod 775 /www/client调整权限(注意安全)。 - 捕获错误信息:一定要捕获命令的错误输出,比如exec的返回码、ssh的stderr、cURL的HTTP状态码,这些信息能快速定位问题(比如权限拒绝、路径不存在)。
内容的提问来源于stack exchange,提问作者S. House

