ssh 远程执行命令,引号为什么会被吃掉
一句话原因
ssh 主机 "命令"
这条命令里的引号要过两层 shell:本地 shell 先解析一遍,把结果拼成字符串交给远端;远端 shell 再解析一遍。任何在本地就被吃掉的引号、变量、反引号,到远端时已经不是你以为的样子了。
三种典型表现
一、报告 unexpected EOF while looking for matching quote
最常见的一种。本地这一层的引号没闭合,bash 直接拒绝执行:
bash: -c: line 1: unexpected EOF while looking for matching `"'
原因通常是想在双引号里再嵌一层双引号,比如:
ssh 主机 "cd /app && echo "done""
本地 shell 眼里这是 "cd /app && echo " 加上 done"",引号配平错了。
二、变量在本地就被展开
ssh 主机 "echo $PATH"
这个 $PATH 是本地的 PATH,因为双引号不阻止变量展开。远端拿到的是一条已经填好值的命令。
想要远端展开,有两种写法:
# 写法一:用单引号(本地不展开)
ssh 主机 'echo $PATH'
# 写法二:转义美元符
ssh 主机 "echo \$PATH"
三、单引号里还要塞单引号
这是最容易翻车的形态,因为单引号内部无法再转义单引号。常见于远端要执行 awk 或 sed:
ssh 主机 'awk '{print $1}' 文件'
本地解析结果与预期完全不同。此时的正确做法不再是加引号,而是换传输方式。
两种稳妥写法
一、heredoc(推荐,长命令首选)
ssh 主机 bash -s <<'EOF'
cd /app
for f in *.log; do
awk '{print $1}' "$f" | sort | uniq -c
done
EOF
要点:
<<'EOF'的引号包住 EOF,表示本地不展开任何变量,原样送过去。- 远端用
bash -s从标准输入读脚本,避免引号层数问题。 - 里面可以有单引号、双引号、美元符、反引号,全部原样。
去掉那个引号(写 <<EOF)就变成"本地先展开再送过去",两种语义按需选。
二、短命令用单引号包裹
ssh 主机 'systemctl is-active 服务名; df -h / | tail -1'
单引号里除了单引号本身,其他字符都原样保留,足够应付大部分巡检类命令。
一个自查方法
不确定远端到底收到了什么,就让它把命令回显出来:
ssh 主机 'echo "$0"; cat /proc/self/cmdline | tr "\0" " "; echo'
或者本地先 echo 一遍,看本地展开后的结果是不是你想要的:
echo "cd /app && echo $PATH"
这一步只花一秒,能省掉大量"为什么远端行为不对"的猜测。
判断清单
现象 | 大概率原因 | 改法
unexpected EOF | 本地引号没配平 | 改用 heredoc
变量值不对(是本机的) | 双引号让本地展开了 | 换单引号或转义 $
远端报 command not found 且名字被截断 | 空格被本地吃掉,参数粘连 | 给参数加引号或换 heredoc
单引号嵌套后行为诡异 | 单引号内不能再转义 | 换 heredoc
小结
ssh 加引号的问题本质是"两层解析"。短命令用单引号包住,长命令用 <<'EOF' 的 heredoc 走标准输入,基本可以避开全部引号坑。不要在双引号里嵌套双引号,也不要指望单引号里能安全塞单引号。
- 点赞
- 收藏
- 关注作者
评论(0)