SSH heredoc 传 PHP 文件:$变量和\命名空间被 shell 吃掉
给远程服务器快速部署一段 PHP 控制器,直接用 SSH heredoc 写进去——看似最省事,结果文件传上去一跑,PHP 直接报语法错误。打开文件一看,所有 $this、$data 都变成了空字符串或者 \$this,更诡异的是 namespace app\api\controller 里的反斜杠也没了,变成了 appapicontroller。
现象
# 执行这条命令把 PHP 文件写到远程
ssh root@server "cat > /www/file.php << 'ENDPHP'
<?php
namespace app\api\controller;
class Foo {
protected \$bar = 'hello';
}
ENDPHP"SSH 执行后,远程文件内容变成:
<?php
namespace appapicontroller; // 反斜杠消失
class Foo {
protected = 'hello'; // $bar 被吃掉
}PHP 运行时立刻抛:
Parse error: syntax error, unexpected '=' in /www/file.php on line 4
根因
Shell 在执行 SSH 命令时,外层双引号 "..." 会触发变量插值:$bar 被当作 shell 变量展开(空字符串),反斜杠被当作转义符吃掉。
虽然内部用了单引号 heredoc << 'ENDPHP',但在某些 shell 版本和嵌套引号场景下行为不一致,且 macOS zsh 与 Linux bash 对 heredoc 引号的处理存在细微差异,难以保证零副作用。
直觉上想用 sed 's/\\$/$/g' 或 tr -d "\\" 把 \$ 还原。但 tr -d "\\" 会删除所有反斜杠,包括:
- PHP 命名空间分隔符:
namespace app\api\controller→appapicontroller - 全局类引用:
catch (\Exception $e)→catch (Exception $e) - 正则表达式:
/\d+/→/d+/
多层 shell 转义让 sed 的替换字符串本身也需要 4~8 个反斜杠才能正确,极易写错。修复工具本身成了第二个坑。
正解
本地写文件,再用 base64 管道无损传输。base64 编码是纯 ASCII,不含任何 shell 特殊字符,传输过程中零插值。
# Step 1:本地正常写文件(无需任何转义)
cat > /tmp/MyController.php << 'EOF'
<?php
namespace app\api\controller;
use think\Db;
class MyController extends BaseController
{
public function index()
{
$data = Db::name('order')->select();
return json($data);
}
}
EOF
# Step 2:base64 编码后管道传到远程解码(macOS 用 -i 标志)
base64 -i /tmp/MyController.php | ssh root@server 'base64 -d > /www/MyController.php'
# Step 3:远程做 PHP 语法检查
ssh root@server 'php -l /www/MyController.php'
# Step 4:验证命名空间和 $ 变量完整
ssh root@server 'grep -n "namespace\|\\$data\|\\\\Exception" /www/MyController.php'- macOS 的
base64用-i <file>指定输入,Linux 用位置参数base64 <file> - 传完必跑
php -l,语法检查会捕获任何残留问题 grep -v fileinfo可过滤掉部分服务器因 fileinfo 模块双加载产生的无关 warning- 文件 < 1MB 时 base64 管道比
scp少一条命令,一行搞定
各方案对比
| 方案 | 问题 |
|------|------|
| SSH heredoc << EOF(双引号)| $ 和 \ 被 shell 插值 |
| SSH heredoc << 'EOF'(单引号)| 理论安全,但嵌套引号/不同 shell 有边缘情况 |
| sed 's/\\$/$/g' 补救 | 多层转义极易出错 |
| tr -d "\\" 补救 | 删除所有反斜杠,命名空间直接崩 |
| scp | 可用,但两条命令;base64 管道一条 |
| base64 管道(推荐) | 零特殊字符,一条命令,完全可靠 |
一句话外卖
凡是要通过 shell 隧道传文本代码,优先选 base64 编码传输——它把任意二进制/文本变成纯 ASCII,彻底切断 shell 插值的可能性。