ssh user@remote_host 'bash -s' << 'EOF'
$(declare -p MY_VAR1 MY_VAR2) # <--- 这行很特殊
echo "Remote received: $MY_VAR1" # <--- 这行需要保护
EOF
1. << 'EOF' 的作用:它主要保护了第二行 echo ... 里的 MY_VAR1 。如果没有单引号,本地 Shell 会尝试去解析 MY_VAR1 。如果本地没有这个变量,它就会变成空字符串发过去,远程就收不到了。
2. $(declare -p ...) 的执行:这个命令之所以能在本地执行,是因为它是命令替换,它的优先级很高,会在 SSH 建立连接之前就被本地 Shell 处理。
● 命令替换 (...) 的优先级:
● 无论外面包着什么(除了单引号这种强制锁死的),只要 Shell 处理到了 (...) ,它就会立即在本地执行括号里的命令。
#!/bin/bash
# 本地定义变量
MY_VAR="HelloFromLocal"
# 1. 先把本地变量传给跳板机
# 2. 跳板机通过管道把脚本传给目标机
ssh user@jump_host "ssh target_user@target_host 'bash -s'" << EOF
# 这里的 \$(declare -p ...) 会在跳板机执行,获取变量定义
# 然后发送给目标机
\$(declare -p MY_VAR)
# 现在在目标机上执行命令,使用刚刚重建的变量
echo "目标机收到变量: \$MY_VAR"
EOF
验证:
# 在本地运行这个(不带单引号的 EOF)
unset MY_VAR1 # 确保本地变量不存在
cat << EOF
$(echo "This runs locally: $(hostname)")
$MY_VAR1 # 这里因为没单引号,本地会尝试展开(结果为空)
EOF
2. 关键点: (...) 的优先级
你之所以能确定它是“连接前”执行的,是因为 (...) 的展开是由本地 Shell 完成的,而不是由 SSH 完成的。
● 如果是: ssh ... << EOF (没有单引号)
● (...) 和 VAR 都会在本地展开。
● 如果是: ssh ... << 'EOF' (有单引号)
● (...) 依然在本地展开(因为它是命令替换,优先级最高)。
● VAR 则被保护,不展开。
#!/bin/bash # 定义变量和函数(用单引号,防止本地展开) vars=' servers=("192.168.1.2") user=fxts source=/app/manager target=/rg/ sourceFile=lpaList targetFile=lppaList cmdFile=a.sh startFile=run.sh ' strFormat=' remoteCMD(){ cd "${target}" cp "${targetFile}" "${targetFile}.$(date +%F)" cat "${sourceFile}" >> "${targetFile}" /usr/bin/bash "${cmdFile}" cd sbin pkill -f nginx || true out=$(/usr/bin/bash "${startFile}" start) if echo "$out" | grep -q "emerg"; then exit 3 fi } remoteCMD # 👈 别忘了调用! ' jserver=172.17.69.1 tserver=172.17.86.21 user=fxts # 构造要在 tserver 上执行的完整脚本 full_script="$vars"$'\n'"$strFormat" # 通过跳板机执行:本地 -> jserver -> tserver ssh -o ConnectTimeout=10 "${user}@${jserver}" \ "ssh \"${user}@${tserver}\" \"bash -c \$(printf '%q' '$full_script')\""