You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

纯Shell执行正常的Docker命令在expect脚本中运行报错是什么原因?

问题底层原理

你遇到的报错是两层解析规则叠加导致的:

  • 第一层:Bash Here-Doc 提前展开
    Bash 中如果 Here-Doc 的界定符(比如你脚本里的END)没有被单引号包裹,Bash 会在将内容传给 Expect 进程之前,先解析所有$开头的变量、命令替换($())、反引号内容,把本地执行的结果直接替换到字符串里。你原脚本里的$(docker ps -a | grep Exited | wc -l)会先在本地服务器执行,把返回的数字(比如本地没有 Exited 容器就返回 0)直接替换进 send 的字符串里,根本不会传到远端执行。
  • 第二层:Expect 自身语法解析
    Expect 是基于 Tcl 语言开发的,Tcl 中双引号包裹的字符串会默认做变量/命令替换,而[]在 Tcl 中是内联命令执行的语法,和 Shell 里的$()作用完全一致。所以就算 Bash 没提前展开,你把[ $(xxx) -ne 0 ]放在 send 的双引号里,Expect 也会尝试把方括号里的内容当作 Tcl 命令执行,自然会报invalid command name "0"这类错误。
规范写法

根据是否需要保留外层 Bash 变量的展开能力,有两种常用解决方案:

方案1:完全关闭提前展开,原样发送命令(无外层变量依赖时推荐)

如果不需要在 Expect 段中使用外层 Bash 定义的变量,直接给 Here-Doc 的界定符加单引号,同时 send 命令的参数用 Expect 的大括号{}包裹即可:大括号在 Tcl 中是纯字符串标识,不会做任何解析替换,确保命令原样发送到远端 Shell 执行。

#!/bin/bash
while read ip pass
do
{
    # 给END加单引号,告诉Bash不要解析Here-Doc内的任何特殊字符
    /usr/bin/expect <<-'END'
    spawn ssh root@$ip
    expect {
        "yes/no" { send "yes\r";exp_continue }
        "password:" { send "$pass\r" }
    }
    expect "#"
    # 大括号包裹要发送的命令,最后加\r触发远端执行
    send { [ $(docker ps -qf status=exited | wc -l) -ne 0 ] && docker start $(docker ps -qf status=exited) }
    send "\r"
    expect eof
    END
}&
done<apps_ip.txt

注:这里的Docker命令做了优化,用原生过滤参数-qf status=exited替代grep+cut,避免Docker输出格式变动导致的解析错误。

方案2:保留外层Bash变量展开,手动转义特殊字符(适配你的场景)

你当前的脚本需要用到外层 Bash 读取的$ip、$pass变量,不能给界定符加单引号,此时只要给所有需要传到远端执行的$、[、]前加反斜杠转义即可:

#!/bin/bash
while read ip pass
do
{
    /usr/bin/expect <<-END
    spawn ssh root@$ip
    expect {
        "yes/no" { send "yes\r";exp_continue }
        "password:" { send "$pass\r" }
    }
    expect "#"
    # 给远端需要保留的$、[、]加反斜杠转义,末尾加\r触发执行
    send "\[ \$(docker ps -qf status=exited | wc -l) -ne 0 \] && docker start \$(docker ps -qf status=exited)\r"
    expect eof
    END
}&
done<apps_ip.txt

补充简化方案

如果不需要在SSH会话中做复杂交互,你可以直接用sshpass传密码执行命令,不需要嵌套Expect,完全避免转义问题:

#!/bin/bash
while read ip pass
do
{
    sshpass -p "$pass" ssh -o StrictHostKeyChecking=no root@$ip \
    '[ $(docker ps -qf status=exited | wc -l) -ne 0 ] && docker start $(docker ps -qf status=exited)'
}&
done<apps_ip.txt

内容的提问来源于stack exchange,提问作者zjhleo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 07:36:03