返回博客

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/tr 修复 \$ 会破坏命名空间反斜杠

直觉上想用 sed 's/\\$/$/g'tr -d "\\"\$ 还原。但 tr -d "\\" 会删除所有反斜杠,包括:

  • PHP 命名空间分隔符:namespace app\api\controllerappapicontroller
  • 全局类引用: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'
正解:base64 管道 = 零转义、零 shell 插值
  • 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 插值的可能性。