基础知识

Expect用于自动控制交互式应用程序,如Telnet,FTP,passwd,fsck,rlogin,tip,SSH等。Expect使用伪终端(Unix)或模拟控制台(Windows),启动目标程序,然后通过终端或控制台界面与人类进行通信。Tk,另一个Tcl扩展,可用于提供GUI。

Expect具有正则表达式模式匹配和通用程序功能,允许简单的脚本智能地控制Telnet,FTP和SSH 等程序,所有这些程序都缺少编程语言,宏或任何其他程序机制。

用法

Expect充当将现有实用程序链接在一起的“粘合剂”。一般的想法是试图弄清楚如何使Expect使用系统的现有工具,而不是弄清楚如何解决Expect内部的问题。

Expect的一个关键用途涉及商业软件产品。其中许多产品提供某种类型的命令行界面,但这些通常缺乏编写脚本所需的功能。它们是为管理产品的用户提供服务而构建的,但公司通常不会花费资源来完全实现强大的脚本语言。Expect脚本可以生成shell,查找环境变量,执行一些Unix命令以检索更多信息,然后进入产品的命令行界面,并配备必要的信息以实现用户的目标。在产品的命令行界面中查找信息后,脚本可以明智地决定采取什么操作(如果有的话)。

每次完成Expect操作时,结果都存储在名为$ expect_out的局部变量中。这允许脚本收集信息以反馈给用户,并且还允许根据情况对接下来发送的内容进行条件行为。

Expect的一个常见用途是建立一个测试套件,无论是程序,实用程序还是嵌入式系统。DejaGnu是一个使用Expect编写的测试套件,用于测试。它已被广泛地用于测试GCC和非常[ 引证需要 ]适合于测试远程目标比如嵌入式开发。

可以使用名为“autoexpect”的工具自动生成expect脚本。此工具会观察您的操作并使用启发式方法生成期望脚本。虽然生成的代码可能很大并且有点神秘,但是总是可以调整生成的脚本以获得确切的代码。

# 假设 $remote_server, $my_user_id, $my_password, $my_command 存在于脚本中
#打开远程服务器的telnet会话,等待用户名提示符.
spawn telnet $remote_server
expect "username:"
# 发送用户名,然后等待密码提示.
send "$my_user_id\r"
expect "password:"
# 发送密码,然后等待shell提示符.
send "$my_password\r"
expect "%"
# 发送预建命令,然后等待另一个shell提示符.
send "$my_command\r"
expect "%"
# 将命令的结果捕获到变量中。这可以显示或写入磁盘
set results $expect_out(buffer)
# 退出telnet会话,等待特殊的文件结束字符
send "exit\r"
expect eof

另一个例子是自动化FTP的脚本:

 # 设置超时的值.
 # 例如,文件很大,网络速度是一个问题,
 # 您最好将此参数设置为一个值.
 set timeout -1
 # 打开远程服务器的ftp会话,等待用户名提示符
 spawn ftp $remote_server
 expect "username:"
 # 发送用户名,然后等待密码提示。
 send "$my_user_id\r"
 expect "password:"
 # 发送密码,然后等待ftp提示符。
 send "$my_password\r"
 expect "ftp>"
 # 切换到二进制模式.
 send "bin\r"
 expect "ftp>"
 # 关闭提示。
 send "prompt\r"
 expect "ftp>"
 # 获取所有文件
 send "mget *\r"
 expect "ftp>"
 # 退出ftp会话,等待特殊的文件结束字符。
 send "bye\r"
 expect eof

下面是一个自动化SFTP(带密码)的示例:

#!/usr/bin/env expect -f

# procedure尝试连接;result 0,如果OK,否则为1 
proc connect {passw} {
  expect {
     "Password:" {
      send "$passw\r"
         expect {
           "sftp*" {
             return 0
            }
        }
     }
  }
  # timed out
  return 1
}

#读取输入参数
set user [lindex $argv 0]
set passw [lindex $argv 1]
set host [lindex $argv 2]
set location [lindex $argv 3]
set file1 [lindex $argv 4]
set file2 [lindex $argv 5]

#puts "Argument data:\n";
#puts "user: $user";
#puts "passw: $passw";
#puts "host: $host";
#puts "location: $location";
#puts "file1: $file1";
#puts "file2: $file2";

#检查
if { $user == "" || $passw == "" || $host == "" || $location == "" || $file1 == "" || $file2 == "" }  {
  puts "Usage: <user> <passw> <host> <location> <file1 to send> <file2 to send>\n"
  exit 1
}

#sftp到指定主机并发送文件
spawn sftp $user@$host

set rez [connect $passw]
if { $rez == 0 } {
  send "cd $location\r"
  set timeout -1
  send "put $file2\r"
  send "put $file1\r"
  send "ls -l\r"
  send "quit\r"
  expect eof
  exit 0
}
puts "\n连接到服务器错误: $host, user: $user and password: $passw!\n"
exit 1

应该注意的是,使用密码作为命令行参数,就像在这个例子中一样,是一个巨大的安全漏洞,因为机器上的任何其他用户都可以通过运行“ ps ” 来读取此密码。但是,您可以添加代码来提示您输入密码,而不是将密码作为参数。这应该更安全。请参阅下面的示例。

stty -echo
send_user -- "Enter Password: "
expect_user -re "(.*)\n"
send_user "\n"
stty echo
set PASS $expect_out(1,string)

自动ssh登录的另一个例子

#imeout是expect中的预定义变量,默认情况下设置为10秒
#spawn_id是expect中的另一个默认变量。
set timeout 60
spawn ssh $user@machine
while {1} {
  expect {

    eof                          {break}
    "The authenticity of host"   {send "yes\r"}
    "password:"                  {send "$password\r"}
    "*\]"                        {send "exit\r"}
  }
}
wait
close $spawn_id

expect 命令实例:

Linux的Bash命令行(A-Z排序)