使用 expect 上传静态资源到服务器

编辑于2018年10月23日

前段时间写了一篇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
  1. 告诉操作系统执行此脚本时使用哪个解释器。
  2. 设置 timeout 变量,此变量用于执行命令执行的超时时间,如果 expect 某个命令的输出时,超过了指定的时间,expect 就执行下面的代码。
  3. 新建一个进程,执行 ssh 命令。
  4. 等待 ssh 命令的返回结果,期望输出信息匹配 *password:,即请求密码。
  5. 发送交互信息,这里发送 123\r,相当于手动输入 “123” 并按下回车。
  6. 脚本执行完后保持互状态,把控制权交给控制台,这个时候就可以手工操作了。

编写脚本

目录结构如下:

.
├── 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

希望对您能有帮助,打赏随意