自动化部署的场景
比如我在本地和服务端都使用 git 来管理代码,当本地代码修改过后,想将改动升级到服务端。这时,我应该先将本地的代码 commit、合并到发布分支、push,然后再 ssh 登录到服务器,在服务器上执行git pull,将代码拉下来。这个过程看似简单,但是如果每天需要执行多次的话,就非常麻烦了。
简单的实现一个 sh 脚本,比如下面
ssh -p 121 root@x.x.x.x
cd /www/wwwroot/xx.com
git pull
执行此脚本,会发现执行到第一行之后,终端会阻塞,因为 ssh 命令是一个阻塞命令,后续的命令将无法执行。
expect 可以实现:开启一个子进程,然后监控这个子进程的输出,按照输出的内容来决定下一步执行的命令。在上面的例子中,就是开启一个子的 ssh 登录的进程,然后监控这个子进程的输出,这样就可以根据输出来决定下面执行的命令啦。
expect 的工作逻辑
先看一个简单的 expect 脚本。
echo '这是一个 shell 脚本'
/usr/bin/expect << eof
set timeout -1
spawn ssh -p 121 root@x.x.x.x
expect "root@*"
send "cd /www/wwwroot/xx/ \r"
expect "root@*"
send "mysqldump -uroot -h localhost -ppassword dbname > back.sql \r"
expect "root@*"
send "zip db.zip back.sql \r"
expect "root@*"
send "exit \r"
eof
echo 'shell 脚本结束'
- expect是一个开发语言,通过/usr/bin/expect进入expect的执行环境。
/usr/bin/expect <<eof
和eof
之间是 expect 脚本,而不是shell脚本。 - mac预装了expect,这里
/usr/bin/expect
是 expect 的安装目录 - spawn 指令的含义为开启一个进程,并将这个子进程的标准输入、输出和错误输出重定向到当前 Expect 环境中,从而实现对子进程的监控和控制。这里 spawn 执行的是 ssh 命令,他会将 ssh 进程的 io 结果输出给 expect,所以 expect 才可以实现按照期望输出值来执行命令的能力。
- expect 后是期望内容的表达式
- send 是发送的命令
通过上面 几点估计你就可以很明白 expect 的工作逻辑了。上面的脚本实现了 ssh 登录一个服务器,然后导出一个数据库并将 导出的 sql 文件压缩。
注意事项
-
使用大括号的语法发送的命令将异步执行,不使用大括号发送的命令将同步执行。等待同步执行结果后才执行expect匹配。如:
expect "root@*" {send "cd /www/dist/web/ \r"}
此命令将异步执行,如:
expect "root@*" send "cd /www/dist/web/ \r" expect "root@*"
此命令将同步执行,直到cd命令的结果返回才执行下一expect命令。
-
使用-d参数可以用调试模式执行命令,这在调试的时候非常有用。如:
/usr/bin/expect-d
<<eof -
send执行的命令有超时限制,如果想设定为无限等待阻塞命令,可以使用timeout选项。如:
echo '🗂 dump 数据 并 zip ' /usr/bin/expect << eof set timeout -1 spawn ssh -p 1222 root@x.x.x.x send "cd /www/wwwroot/xxx.com\r" expect "root@*" send "zip db.zip db.sql \r" expect "root@*" send "xxxxx" eof echo done!
-
expect是一种编程语言,expect和send是此语言中两个独立的命令。一般来讲一行expect,一行send。
one more thing
服务的自动化部署是一个常见的需求,尤其是当服务较多时。比如一个服务需要部署到多台服务器(负载均衡或者私有化部署)。这时手动部署就成了灾难。麻烦且不稳定,一个不留神就会导致不可预料的结果。此时有两种解决方案。
- 自己实现一套自动化部署的脚本,实现本地打包->上传到服务器->自动解压等部署操作的自动化;
- 使用专业的工具比如jenkins;
前者成本较低,但是功能较弱,适合小型项目。后者功能强大,但学习和部署成本较高,适合大型项目(当然如果有jenkens的部署经验,小项目也同样适用)。