记录使用expect遇到的一个坑

记录使用 expect 遇到的一个坑


1. 问题描述

最近写了一个自动化的安装程序,由于要远程操作多台服务器进行安装,所以必然想到了使用 Shell 脚本来自动化。而设计到密码输入的(比如 sudo )则使用 expect 来进行交互,然后被一个小坑浪费了一个下午的时间。

先上代码:

#!/bin/bash
user=${1}
ip=${2}
passwd=${3}


expect -c "
spawn ssh ${user}@${ip} -t 'uname -a'
expect {
    assword: {
        send \"${passwd}\r\"
    }
}
expect eof
"

本质上就是连接到服务器执行命令而已,uname -a 但是脚本竟然报错了:zsh:1: command not found: uname -a(注:真实执行的命令可不是 uname -a 这么简单,这里做示例足够了)。

WTF?

2. 解决过程

想了半天,我一直以为是使用 ssh 登录过去环境变量不对导致的。花了半天时间研究了ssh连接远程主机执行脚本的环境变量问题 这篇文章。虽然没有解决问题,但是文章不错!

苦恼良久,我看到了 send \"${passwd}\r\" 这段代码感到很奇怪。这段 expect 代码是从一个前同事的脚本中扒下来的,整条 expect 命令是由双引号括起来的,字符串参数值替换应该没问题,为什么一定要用转义的双引号呢?

我猜这里可能有什么关键,所以果断把 'uname -a' 改成 \"uname -a\"

结果跑通了!

接下来我又发现 'uname -a' 改成 'uname' 后原来的代码也能跑。

测试了一下午加一个晚上总结出,总结出三种可以跑通的情况:

  1. 不在 expect 命令中而直接以 ssh <user>@<ip> <cmd> 的形式调用。
  2. 'uname -a' 改成 \"uname -a\"
  3. 'uname -a' 改成 'uname'

所以我猜测应该是在使用 expect 时命令体内部在表示 <cmd> 部分出了问题。当命令带 flag 时,单引号没有起到作用,需要使用转义的双引号。

其底层原因我也没时间去探索了,在此 mark 一下吧。

3. 结论

总结来说,expect 命令内部需要用双引号和单引号的还是尽量用转义的双引号吧!