用AI Agent写脚本时,一个SSH引号坑我排查了四十分钟
背景是这样:我有个跑在远程服务器上的运维脚本 zhihui_ops.py,本地写了篇六百多字的稿子放在 /tmp/post.md,想通过 SSH 把它喂给脚本的参数。第一版命令我写得很顺手:ssh root@49.233.10.139 python3 /root/scripts/zhihui_ops.py --content "$(cat /tmp/post.md)"。敲回车,脚本立刻回了一句正文过短,六十字都不到。我反复确认文件确实有六百多字,cat 出来也正常,一度以为是脚本的字数统计逻辑写错了。
第一个坑:命令替换发生在本地。$(cat /tmp/post.md) 这一对双引号,是给本地 shell 解析的。shell 在把整条命令交给 ssh 之前,就先把 /tmp/post.md 的内容读出来、拼进命令字符串,再发送。所以真正的问题不在远程脚本,而在于我拼出来的那条命令本身长什么样。文件里只要有反引号、美元符号、或者未转义的引号,本地 shell 会先按自己的规则处理一遍,内容在传输前就已经被改写。
第二个坑:文件路径其实不存在于远程。我下意识以为 cat 是在服务器上跑的,可本地文件根本没有同步过去。所以在另一种写法里(把命令用单引号包住、只让远程展开),远程会直接报 No such file or directory。两种写法各有各的错:双引号在本地展开,单引号在远程展开,路径和转义都得跟着走。我用一组最小实验确认了这点,本地起一个只有三行、带空行的文本,两种引号各跑一次,一次报文件不存在,一次内容被本地吃掉了换行。
关键结论有三条,都是可复用的。第一,凡是参数里有换行、引号、反引号、美元符号的内容,不要用命令替换去拼,改用标准输入或临时文件中转。我最终用的是把稿子通过 stdin 传进去,命令里完全不出现文件内容,本地 shell 没有机会改写它。第二,判断展开发生在哪一侧,只看引号:双引号归本地,单引号归远程,这不是风格问题,是执行位置的硬约束。第三,验证要能证伪。我一开始只在本地 cat 文件,看到内容正确就放心了,但这根本没验证传输链路。真正有用的验证是让远程把那串参数回显出来或写进日志,看到的内容才算数。
这一路最值得记的其实不是某个语法,而是排查顺序:报错说内容太短时,我先怀疑的是脚本的字数统计,而正确动作是先确认脚本收到的字符串到底长什么样。把中间态打出来,比逐行读代码快得多。后来我把这组三行文本和两个引号写法的对照,留成了一个随手能跑的自检片段,再遇到类似问题,三十秒就能定位是本地展开还是远程缺文件,不用再花四十分钟。