Expect 是用于自动化交互式应用程序 
 
1. 软件介绍 现代的 Shell 对程序提供了最小限度的控制(程序的开始/停止/关闭等),而把交互的特性留给了用户。这意味着有些程序,你不能非交互的运行,比如说 passwd 命令。有一些程序可以非交互的运行,但在很大程度上丧失了灵活性,比如说 fsck 命令。这表明 Unix 的工具构造逻辑开始出现问题。Expect 恰恰填补了其中的一些裂痕,解决了在 Unix 环境中长期存在着的一些问题。
Expect 使用 Tcl 作为语言核心,不管程序是交互和还是非交互的,Expect 都能运用。Tcl 实际上是一个子程序库,这些子程序库可以嵌入到程序里从而提供语言服务。 最终的语言有点象一个典型的 Shell 语言。里面有给变量赋值的 set 命令,控制程序执行的 if, for, continue 等命令,还能进行普通的数学和字符串操作。
Expect 是在 Tcl 基础上创建起来的并且还提供了一些 Tcl 所没有的命令:
spawn 命令激活一个 Unix 程序来进行交互式的运行send 命令向进程发送字符串expect 命令等待进程的某些字符串且支持正规表达式并能同时等待多个字符串 
1 2 expect patlist1 action1 patlist2 action2..... 
该命令一直等到当前进程的输出和以上的某一个模式相匹配,或者等到时间超过一个特定的时间长度,或者等到遇到了文件的结束为止。每一个 patlist 都由一个模式或者模式的表(lists)组成。如果有一个模式匹配成功,相应的 action 就被执行,执行的结果从 expect 返回。
被精确匹配的字符串(或者当超时发生时,已经读取但未进行匹配的字符串)被存贮在变量 expect_match 里面。如果 patlist 是 eof 或者 timeout 的情况,则发生文件结束或者超时时才执行相应的 action 动作。一般超时的默认值是 10 秒,但可以用类似 "set timeout 30" 之类的命令把超时时值设定为 30 秒。
1 2 3 4 5 6 expect "*welcome*"  break    "*busy*"  {print  busy;continue }   "*failed*"  abort   timeout abort 
模式是通常的 C Shell 风格的正规表达式 ,模式必须匹配当前进程的从上一个 expect 或者 interact 开始的所有输出(所以统配符*****使用的非常的普遍)。但是,一旦输出超过 2000 个字节,前面的字符就会被忘记,这可以通过设定 match_max 的值来改变。
字符可以使用反斜杠来单独的引用,反斜杠也被用于对语句的延续,如果不加反斜杠的话,语句到一行的结尾处就结束了。这和 Tcl 也是一致的。Tcl 在发现有开的单引号或者开的双引号时都会继续扫描。而且,分号可以用于在一行中分割多个语句。这乍听起来有点让人困惑,但是,这是解释性语言的风格,但是,这确实是 Tcl 的不太漂亮的部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 spawn passwd [lindex $argv  1] expect eof               {exit  1}     timeout              {exit  2}     "*No such user.*"     {exit  3}     "*New password:"  send "[index $argv  2]\r"  expect eof                   {exit  4}     timeout                  {exit  2}     "*Password too long*"     {exit  5}     "*Password too short*"    {exit  5}     "*Retype new password:"  send "[index $argv  3] "  expect timeout               {exit  2}     "*Mismatch*"              {exit  6}     "*Password unchanged*"    {exit  7}     " "  expect timeout               {exit  2}     "*"                       {exit  6}     eof 
这个脚本退出时用一个数字来表示所发生的情况。0 表示 passwd 程序正常运行,1 表示非预期的死亡,2 表示锁定,等等。使用数字是为了简单起见。expect 返回字符串和返回数字是一样简单的,即使是派生程序自身产生的消息也是一样的。实际上,典型的做法是把整个交互的过程存到一个文件里面,只有当程序的运行和预期一样的时候才把这个文件删除。否则这个 log 被留待以后进一步的检查。
这个 passwd 检查脚本被设计成由别的脚本来驱动。这第二个脚本从一个文件里面读取参数和预期的结果。对于每一个输入参数集,它调用第一个脚本并且把结果和预期的结果相比较。(因为这个任务是非交互的,一个普通的老式 shell 就可以用来解释第二个脚本)。比如说,一个 passwd 的数据文件很有可能就象下面一样。
1 2 3 4 5 6 passwd.exp    3    bogus    -        - passwd.exp    0    fred    abledabl    abledabl passwd.exp    5    fred    abcdefghijklm    - passwd.exp    5    fred    abc        - passwd.exp    6    fred    foobar        bar passwd.exp    4    fred    ^C        - 
第一个域的名字是要被运行的回归脚本。第二个域是需要和结果相匹配的退出值。第三个域就是用户名。第四个域和第五个域就是提示时应该输入的密码。减号仅 仅表示那里有一个域,这个域其实绝对不会用到。在第一个行中,bogus 表示用户名是非法的,因此 passwd 会响应说:没有此用户。expect 在退出时会返回 3,3 恰好就是第二个域。在最后一行中,^C 就是被切实的送给程序来验证程序是否恰当的退出。
2. 工具安装 
源代码和下载地址都是由 Linux 软件基金会维护的(sourceforge) 
 
1 2 3 4 5 6 $ sudo yum install -y gcc $ sudo yum install -y tcl tclx tcl-devel $ sudo yum  install expect 
1 2 3 4 5 6 7 $ sudo apt install -y gcc $ sudo apt install tcl $ sudo apt install expect 
1 2 3 4 5 6 7 8 9 10 11 wget "https://sourceforge.net/projects/expect/files/Expect/5.45.4/expect5.45.4.tar.gz/download"  $ sudo ./configure \     --with-tcl=/usr/lib \     --with-tclinclude=/usr/include/tcl-private/generic $ sudo make && make install 
3. 基础知识 
主要介绍常见的 4 个命令的使用方式 
 
我们知道,send 命令用于发送信息到进程中,expect 命令则是根据进程反馈的信息进行对应逻辑的交互的。而 spawn 命令后的 send 和 expect 命令其实都是和使用 spawn 命令打开的进程进行交互的。 
需要说明的是 interact 命令其实用的不多,一般情况下使用 spawn、send 和 expect 命令就可以很好的完成任务了。但在一些特殊场合下,使用 interact 命令还是能够发挥很好作用的。interact 命令主要用于退出自动化进入人工交互。比如我们使用 spawn、send 和 expect 命令完成了 ftp 登陆主机,执行下载文件任务,但是我们希望在文件下载结束以后,仍然可以停留在 ftp 命令行状态,以便手动的执行后续命令,此时使用 interact 命令就可以很好的完成这个任务。 
 
编号 
命令 
作用 
 
 
1 
sendsend 命令接收一个字符串并将该参数发送到进程中 
 
2 
expectexpect 通常用来等待进程的反馈再发送对应的交互命令 
 
3 
spawnspawn 命令用来启动新的进程 
 
4 
interact允许退出自动化进入人工交互 
 
 
4. 控制结构 
介绍 TCL 语言的控制结构 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 set  timeout 10set  alias_host [lindex $argv  0]set  b1_password ASJZOMxlgM^9set  b2_password a0yDuePSLUGMif  {$argc !=1} {    echo  "请输入想要远程连接的服务器: [b1|b2]"      exit  1 } if  {$alias_host =="b1" } {    spawn ssh escape@192.168.100.100 -p 22     expect "*password*"  {send "$b1_password \r" }     interact } elseif {$alias_host =="b2" } {     spawn ssh escape@192.168.100.101 -p 22     expect "*password*"  {send "$b2_password \r" }     interact } else  {     send "请输入想要远程连接的服务器: [b1|b2]"  } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 set  timeout 10set  alias_host [lindex $argv  0]set  b1_password ASJZOMxlgM^9set  b2_password a0yDuePSLUGMswitch -glob -- $file1  {     b1 {         spawn ssh escape@192.168.100.100 -p 22         expect "*password*"  {send "$b1_password \r" }         interact     }     b2 {         spawn ssh escape@192.168.100.101 -p 22         expect "*password*"  {send "$b2_password \r" }         interact } 
1 2 3 4 5 6 7 8 9 10 set  test  0while  {$test <10} {    set  test  [expr {$test  + 1}]     if  {$test  > 7}         break      if  "$test  < 3"          continue  } 
1 2 3 4 5 6 7 8 proc Error {} {     error "This is a error for test"  } catch Error test  puts $test  
5. 简单使用 
下面是一些简单的示例代码,主要帮助我们理解 expect 的使用。 
 
1 2 3 4 5 6 7 [escape@linuxworld ~]$ passwd Changing password for  user escape. Changing password for  escape. (current) UNIX password: New password: Retype new password: passwd: all authentication tokens updated successfully. 
1 2 3 4 5 6 7 8 9 10 set  timeout 30spawn passwd [lindex $argv  1] set  password [lindex $argv  2]expect "*New password:*"  {send "$password \r" } expect "*Retype new password:*"  {send "$password \r" } expect eof 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 set  timeout 30  set  host "100.200.200.200" set  username "root" set  password "123456" spawn ssh $username @$host  expect {          "*yes/no"  {send "yes\r" ;exp_continue}          "*password*"  {send "$password \r" } } interact 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 if  {$argc  < 3} {    puts "Usage:cmd <host>:<username> -p <port>"      exit  1 } set  timeout 30set  host [lindex $argv  0]set  username [lindex $argv  1]set  password [lindex $argv  2]set  port [lindex $argv  3]spawn ssh $username @$host  -p $port  expect "*password*"  {send "$password \r" } interact 
1 2 3 4 5 6 7 8 #!/bin/bash read  -p "please input you user:"  -t30 remote_userread  -p "please input you ip:"  -t30 remote_ipread  -p "please input you port:"  -t30 remote_portecho  "ssh $remote_user :$remote_ip  -p $remote_port " ./login.exp $remote_user  $remote_ip  $remote_port  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #!/bin/bash read  -p "please input you user:"  -t30 remote_userread  -p "please input you ip:"  -t30 remote_ipread  -p "please input you port:"  -t30 remote_portecho  "ssh $remote_user :$remote_ip  -p $remote_port " expect -d <<EOF spawn ssh $remote_ip  expect { "*yes/no"  {send "yes\r" ;exp_continue}"*password:"  {send "Xuansiwei123!\r" }} exit expect eof; EOF 
6. 高级示例 
弄懂下面的高级玩法,就可以应对日常的工作使用了。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 set  ip [lindex $argv  0 ]         set  userid [lindex $argv  1 ]     set  mypassword [lindex $argv  2 ] set  mycommand [lindex $argv  3 ]  set  timeout 10                   spawn telnet $ip      expect "username:"           send "$userid \r"      expect "password:"           send "$mypassword \r"      expect "%"           send "$mycommand \r"      expect "%"           set  results $expect_out (buffer)          send "exit\r"      expect eof 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 set  ip [lindex $argv  0 ]         set  userid [lindex $argv  1 ]     set  mypassword [lindex $argv  2 ] set  timeout 10                   spawn ftp $ip      expect "username:"           send "$userid \r"      expect "password:"           send "$mypassword \r"      expect "ftp>"           send "bin\r"      expect "ftp>"           send "prompt\r"      expect "ftp>"           send "mget *\r"      expect "ftp>"           send "bye\r"      expect eof 
1 2 3 4 5 6 7 8 9 10 11 12 13 set  ip [lindex $argv  0 ]         set  username [lindex $argv  1 ]   set  mypassword [lindex $argv  2 ] set  timeout 10                   spawn ssh $username @$ip         expect {                           "*yes/no"  { send "yes\r" ; exp_continue}       "*password:"  { send "$mypassword \r"  }     } interact         
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 set  IP     [lindex $argv  0]set  USER   [lindex $argv  1]set  PASSWD [lindex $argv  2]set  CMD    [lindex $argv  3]spawn ssh $USER @$IP  $CMD  expect {     "(yes/no)?"  {         send "yes\r"          expect "password:"          send "$PASSWD \r"          }     "password:"  {send "$PASSWD \r" }     "* to host"  {exit  1}     } expect eof 
[5] 批量登录 ssh 服务器执行操作范例 => for 循环  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 for  {set  i 10} {$i  <= 12} {incr i} {    set  timeout 30     set  ssh_user [lindex $argv  0]     spawn ssh -i .ssh/$ssh_user  abc$i .com     expect_before "no)?"  {     send "yes\r"  }     sleep 1     expect "password*"      send "hello\r"      expect "*#"      send "echo hello expect! > /tmp/expect.txt\r"      expect "*#"      send "echo\r"  } exit 
[6] 批量登录 ssh 并执行命令 => foreach 语法  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 if  {$argc !=2} {    send_user "usage: ./expect ssh_user password\n"      exit  } foreach i {11 12} {     set  timeout 30     set  ssh_user [lindex $argv  0]     set  password [lindex $argv  1]     spawn ssh -i .ssh/$ssh_user  root@xxx.yy.com     expect_before "no)?"  {     send "yes\r"  }     sleep 1     expect "Enter passphrase for key*"      send "password\r"      expect "*#"      send "echo hello expect! > /tmp/expect.txt\r"      expect "*#"      send "echo\r"  } exit 
[7] 批量 ssh 执行命令 => 用 shell 调用 tclsh 方式、多进程同时执行  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #!/bin/sh exec  tclsh $0  "$@ " package require Expect set  username [lindex $argv  0]set  password [lindex $argv  1]set  argv [lrange $argv  2 end]set  prompt "(%|#|\\$) $" foreach ip $argv  {     spawn ssh -t $username @$ip  sh     lappend ids $spawn_id  } expect_before -i ids eof {     set  index [lsearch $ids  $expect_out (spawn_id)]     set  ids [lreplace $ids  $index  $index ]     if  [llength $ids ] exp_continue } expect -i ids "(yes/no)\\?"  {     send -i $expect_out (spawn_id) yes\r     exp_continue } -i ids "Enter passphrase for key"  {     send -i $expect_out (spawn_id) \r     exp_continue } -i ids "assword:"  {     send -i $expect_out (spawn_id) $password \r     exp_continue } -i ids -re $prompt  {     set  spawn_id $expect_out (spawn_id)     send "echo hello; exit\r"      exp_continue } timeout {     exit  1 } 
[8] 使用 ssh 自动登录 expect 脚本 => ssh.expect  
1 2 3 4 5 6 7 8 9 10 11 12 The authenticity of host '192.168.xxx.xxx (192.168.xxx.xxx)'  can't be established.  RSA key fingerprint is 25:e8:4c:89:a3:b2:06:ee:de:66:c7:7e:1b:fa:1c:c5. Are you sure you want to continue connecting (yes/no)? Warning: Permanently added ' 192.168.xxx.xxx' (RSA) to the list of known hosts. Enter passphrase for key ' /data/key/my_dsa': Last login: Sun Jan 26 13:39:37 2019 from 192.168.xxx.xxx [root@master003 ~]# root@192.168.xxx.xxx' s password:Last login: Thu Jan 23 17:50:43 2019 from 192.168.xxx.xxx 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 if  {$argc  < 4} {    send_user "Usage:\n  $argv0  IPaddr User Passwd Port Passphrase\n"      puts stderr "argv error!\n"      sleep 1     exit  1 } set  timeout 30set  ip         [lindex $argv  0 ]set  user       [lindex $argv  1 ]set  passwd     [lindex $argv  2 ]set  port       [lindex $argv  3 ]set  passphrase [lindex $argv  4 ]if  {$port  == "" } {    set  port 22 } spawn ssh $user @$ip  -p $port  expect_before "(yes/no)\\?"  {     send "yes\r" } expect \ "Enter passphrase for key*"  {    send "$passphrase \r"      exp_continue } " password:?"  {     send "$passwd \r"      exp_continue } "*\[#\\\$]"  {     interact } "* to host"  {     send_user "Connect faild!"      exit  2 } timeout {     send_user "Connect timeout!"      exit  2 } eof {     send_user "Lost connect!"      exit  } 
7. 参考博客 
本文转载自:「 Escape 的博客 」,原文:https://tinyurl.com/y4wa98s9,版权归原作者所有。欢迎投稿,投稿邮箱:  editor@hi-linux.com 。