前段时间写了一篇Node.js 静态资源打包上传脚本的博客,今天又花了些时间使用 expect 编写了一版。主要完成静态资源打包、上传到服务器、解压到服务器指定目录的功能。
expect 介绍
Expect 是 Unix 系统中用来进行自动化控制和测试的软件工具,作为Tcl脚本语言的一个扩展,应用在交互式软件中如 telnet,ftp,Passwd,fsck,rlogin,tip,ssh 等等。该工具利用 Unix 伪终端包装其子进程,允许任意程序通过终端接入进行自动化控制;也可利用Tk工具,将交互程序包装在 X11 的图形用户界面中。
expect 主要命令
- spawn 新建一个进程,这个进程的交互由 expect 控制。
- expect 等待接受进程返回的字符串,直到超时时间,根据规则决定下一步操作。
- send 发送字符串给expect控制的进程。
- set 设定变量为某个值。
- exp_continue 重新执行expect命令分支。
- [lindex $argv 0] 获取expect脚本的第 1 个参数。
- interact 将脚本的控制权交给用户,用户可继续输入命令。
- expect eof 等待 spawn 进程结束后的退出信号 eof。
基本用法
一个简单地自动 SSH 远程服务器例子如下:
#!/usr/bin/expect // 1
set timeout 20 // 2
spawn ssh root@192.168.1.1 // 3
expect "*password:" // 5
send "123\r" // 6
interact // 7
- 告诉操作系统执行此脚本时使用哪个解释器。
- 设置
timeout
变量,此变量用于执行命令执行的超时时间,如果 expect 某个命令的输出时,超过了指定的时间,expect 就执行下面的代码。 - 新建一个进程,执行
ssh
命令。 - 等待
ssh
命令的返回结果,期望输出信息匹配*password:
,即请求密码。 - 发送交互信息,这里发送
123\r
,相当于手动输入 “123” 并按下回车。 - 脚本执行完后保持互状态,把控制权交给控制台,这个时候就可以手工操作了。
编写脚本
目录结构如下:
.
├── dist
│ ├── index.js
│ ├── index.html
│ ├── index.css
│ ├── ...
└── upload.exp
dist
目录中是所有的静态资源,脚本的主要工作就是把 dist
中内容上传到服务器中的指定目录下。其中,服务器的 IP、用户名、密码以及指定的目录可以通过参数传入。例如将静态资源上传到 192.168.1.1
服务器的 /root/public
文件夹中,用户名为 root
,密码为 root
:
$ expect upload.exp 192.168.1.1 root root /root/public
创建脚本文件
创建与 dist
目录同级的 upload.exp
文件,并在文件第一行指定解释器:
#!/usr/bin/env expect
这里没有使用上面实例中的 #!/usr/bin/expect
是为了防止用户没有把 expect
装在默认的 /usr/bin
中。
读取参数
使用 [lindex $argv]
读取参数:
set host [lindex $argv 0]
set username [lindex $argv 1]
set password [lindex $argv 2]
set target [lindex $argv 3]
打包静态资源
为了便于上传到服务器中,首先调用 tar
命令将资源打包,这里需要注意的时,在打包时不应该把 dist
目录打包进去,而是打包 dist
目录下面的内容,否则解压时会多出 dist
目录。我们可以通过 -C
参数指定工作目录为 dist
,然后打包当前目录下的内容即可:
spawn tar -czvf dist.tar.gz -C dist .
然后加上 expect eof
等待 spawn 进程结束。
上传压缩包到服务器
打包完毕后,调用 scp
命令将压缩包上传到服务器上的指定目录中,这里需要使用之前读取到的参数:
spawn scp dist.tar.gz $username@$host:$target
上传到服务器时需要输入账户密码:
expect {
"(yes/no)?" {
send "yes\r"
expect "*assword:" {
send "$password\r"
}
}
"*assword:" {
send "$password\r"
}
}
这里需要匹配 (yes/no)?
是因为有时候连接服务器(通常是第一次)会出现提示信息,需要确认是否继续连接:
The authenticity of host '111.222.333.444 (111.222.333.444)' can't be established.
RSA key fingerprint is f3:cf:58:ae:71:0b:c8:04:6f:34:a3:b2:e4:1e:0c:8b.
Are you sure you want to continue connecting (yes/no)?
上传完毕后,会显示进度为 100%,通过 expect 匹配此输出,表明上传完成:
expect "100%"
连接服务器执行远程命令
压缩包上传成功后,我们需要连接到远程服务器,在服务器上进行解压操作。使用 ssh
命令连接服务器,并处理密码:
spawn ssh $username@$host
expect {
"(yes/no)?" {
send "yes\r"
expect "*assword:" {
send "$password\r"
}
}
"*assword:" {
send "$password\r"
}
}
连接到终端后,我们需要匹配远程终端的提示信息:
root@iZ2zeck1ad0ao2rjwcs6u7Z:~$
通过 expect -re "~.*(#|$)"
。其中 -re
参数表明后面是一个正则表达式。如果匹配此信息,表明成功连接上了远程终端。
接着,我们发送远程解压命令即可:
send "tar -zxvf $target_path/dist.tar.gz -C $target_path\r"
解压完成后,可以删除压缩包:
send "rm -r $target_path/dist.tar.gz\r"
完整脚本
#!/usr/bin/env expect
set host [lindex $argv 0]
set username [lindex $argv 1]
set password [lindex $argv 2]
set target [lindex $argv 3]
set cmd_prompt "~.*(#|$)"
# 打包
spawn tar -czvf dist.tar.gz -C dist .
# 上传
expect eof
spawn scp dist.tar.gz $username@$host:$target
expect {
"(yes/no)?" {
send "yes\r"
expect "*assword:" {
send "$password\r"
}
}
"*assword:" {
send "$password\r"
}
}
expect "100%"
send_user "\n"
# SSH
spawn ssh $username@$host
expect {
"(yes/no)?" {
send "yes\r"
expect "*assword:" {
send "$password\r"
}
}
"*assword:" {
send "$password\r"
}
}
# 解压
expect -re $cmd_prompt
send "tar -zxvf $target/dist.tar.gz -C $target\r"
# 删除压缩包
expect -re $cmd_prompt
send "rm -r $target/dist.tar.gz\r"
expect -re $cmd_prompt
exit
网友评论