tcl-expect示例文档

 
环境:
 
1.查看本机tcl 包安装的路径
$>whereis tcl
tcl: /usr/bin/tcl /usr/lib/tcl8.4 /usr/share/tcl8.4
 
2.安装expect
tar -zxvf expect-5.43.0.tar.gz
$>./configure --prefix=/usr \
         --with-tcl=/usr/lib \
         --with-tclinclude=/usr/lib/tcl8.4 \
         --enable-shared
$>make
$>make test
$>make install
 
 
3.Tcl 安装(如果在安装expect时候提示找不到tcl包,或者就你的机器上没有这个包就安装它):
   tar -zxvf tcl8.4.15-src.tar.gz
$>cd unix &&
$>./configure --prefix=/usr --enable-threads && make
$> make test
$> make install && make install-private-headers && ln -v -sf tclsh8.4 /usr/bin/tclsh
 
文件 1. hostlist.txt
#If there are some uesless   IP addresses,you can use "#'before them to remark.---2007-08-29---
192.168.9.12|password|root|/home/tomcat
#192.168.9.13|password|root|/home/tomcat
192.168.9.19|password|root|/home/tomcat
 
文件2. loginssh.exp
 
#!/usr/bin/expect -f
 
##******************************************************************************************
##   Program:     loginssh,    Version 0.2
## Author:     hanson
##    File:     loginssh.exp
## Changelog: 2007.08.02    Version 0.1
##                                   Initial release
##              2007.08.30    Version 0.2
##                                   Bugfix: No error message when timeout
## Description:
##     Given a hostname tomcate path user Password   this script will   Stop Start
##     CheckTomcat.
## Example:
##     chmod +x loginssh.exp
##     ./loginssh.exp 123456 192.168.1.9 root CheckState TomcatPath
##Self-evaluation:The stringent code 3
##******************************************************************************************
 
# Init variables
set MyPasswordexp [lrange $argv 0 0]
set MyServerexp [lrange $argv 1 1]
set MyUserexp [lrange $argv 2 2]
set MyStateexp [lrange $argv 3 3]
set MyTomcatPathexp [lrange $argv 4 4]
 
#set username "hanson" --Variable definitions
set timeout 15
 
#-----------------------------Supporting Functions-------------------------------
 
# expect : wait fom some input  
# send   : send something to th program
# send_user :standard output
# send_error :standard error
# exit : Exit program with appropriate error code
#
#
 
proc Do_TomcatLogin {MyServerexp MyPasswordexp} {
    set timeout 25
   
    #The first method
   
    #expect {
    # -re "Are you sure you want to"
   #           {
    #          send "yes\n"
    #          exp_continue
   #           }
   #        }
    #expect   "Password:"
    #send "$MyPasswordexp\n"
    #expect   "Login:"
    #send_user "\nLogin Successfully...\n"
   
   
    #The second method  
    #expect {
    #     -re "Are you sure you want to"
   #           {
    #          send "yes\n"
    #          exp_continue
   #           }
   #        }
   
    expect {
   -re "to continue" {
      send "yes\n";
      exp_continue
                     }
      "password:" {
                  send "$MyPasswordexp\n";
                  send_user "\nLogin Successfully...\n"
       }
       timeout {
            send_error "Failed to make ssh connection, exiting!\n";
            close;
            wait;
            exit 1
               }
   }
  
  
}
 
 
proc Do_TomcatState {MyServerexp MyTomcatPathexp} {
#
    set timeout 8
    send "\n"
 
    expect "state"
    send "ps -ef | grep java |grep -v 'grep'| awk '{print \"user:\" \$1 \"   PID:\" \$2}'\n"
    expect   "Login:"
    send_user "\nServer IP :$MyServerexp Check State Finished...\n"   
   
}
 
proc Do_TomcatStartup {MyServerexp MyTomcatPathexp} {
#       
   if { $MyServerexp == "192.168.1.12" } {
   set timeout 45
   } else {
   set timeout 15
       }
      
    send "\n"
    expect   "startup"
    send "$MyTomcatPathexp/bin/startup.sh\n"
  
    expect   "startupst"
    send_user "\nServer iP:$MyServerexp start Finished...\n"
}
 
 
proc Do_TomcatStop {MyServerexp MyTomcatPathexp} {
#
    set timeout 15
    send "\n"
    expect   "stop:"
    send "$MyTomcatPathexp/bin/shutdown.sh\n"
    expect   "stopst:"
    send_user "\nServer iP:$MyServerexp stop Finished...\n"
}
 
proc Do_Tomcatkill {MyServerexp} {
#
    set timeout 5
    send "\n"
    expect   "kill"
    send "kill -9 `ps -ef|grep java|grep -v 'grep'|awk '{print \$2}'`\n"
    expect   "killstate:"
    send_user "\nServer iP:$MyServerexp tomcat kill Finished...\n"
}
 
proc Do_T {} {
#Not realizing
    set timeout 10
    send "\n"
    expect -re " $"
    send "ps -auxww|grep "usr"| wc -l\r"
    expect -re " $"
    send "echo $?\r"
   
}
 
if {$argc<5} {
    puts stderr "Usage: $argv0 Password Server User State TomcatPath.\n "
    exit 1
}
 
# *************************End of Supporting Functions*****************************
 
 
#**************************Main program started************************************
# Start a program
spawn   ssh $MyUserexp@$MyServerexp
 
#
Do_TomcatLogin $MyServerexp $MyPasswordexp
switch   $MyStateexp {
      "CheckState" {
                      Do_TomcatState $MyServerexp $MyTomcatPathexp
                     }
                    
             "Start" {
                      Do_TomcatStartup   $MyServerexp $MyTomcatPathexp
                     }
                    
            "Stop" {
                      #Do_TomcatStop $MyServerexp $MyTomcatPathexp
                      #if Do_TomcatStop   Finished   then
                      Do_Tomcatkill $MyServerexp
                     }
                    
                      }                                     
 
#****************************Main program end************************************
close
wait
exit
 
 
 
文件3.   sshlogin.sh
 
#!/bin/bash
 
##******************************************************************************************
## Program:     sshlogin,    Version 0.1
## Author:     hanson
##    File:     sslogin.sh
## Changelog: 2007.07.22    Version 0.1
##                                   Initial release
## Description:
##    Start up the Tomcat engine.
## Example:
##     chmod +x sshlogin.sh
##     ./loginssh.exp state
##Self-evaluation:The stringent code 3
##******************************************************************************************
 
# Source function library.
. /etc/init.d/functions
 
function StrSearch()
# Returns a string header
{
   Str=${1##' '}
   Str1=`expr substr $Str   1 1`
   if [ $Str1 == "#" ]; then
    return 0
   else
    return 1
   fi
}
 
function PingHost()
# Using PING order checks network connectivity
{  
local bool="T"
exec 3<&0        # 把标准输入关联到文件描述符.
exec 0<$FILE     # 重定向标准输入.
while read line       
do
    MyServer=$(echo $line | cut -d '|' -f1)
  
    StrSearch $MyServer
    if [ "$?" -eq 0 ] ; then
       continue      
    fi
   
   
    ping -c 1 -w 1 $MyServer > /dev/null 2>&1;
    if [ "$?" -eq "1" ]; then
       echo   "Can't ping $MyServer Quitting!"                   
       bool="F"
    else
       echo "Ping to $MyServer was successful!"         
    fi      
done
 
exec 0<&3    # 恢复旧的标准输入
 
echo -e "---------------------------------------------------------------Check end\n"                           
if [ "$bool" = "T" ] ; then
   return 0
else
   return 1
fi   
}
 
function Login ()
# Users Login ,relevant file with loginssh.exp
{
   local intstr=1
          exec 3<&0
          exec 0<$FILE
          while read line       
          do
         MyServer=$(echo $line | cut -d '|' -f1)
         MyPassword=$(echo $line | cut -d '|' -f2)
         MyUser=$(echo $line | cut -d '|' -f3)
         MyTomcatPath=$(echo $line | cut -d '|' -f4)
        
         StrSearch $MyServer
         if [ "$?" -eq 0 ] ; then
            continue      
         fi         
                     
         echo "-----------Running ssh $MyUser@$MyServer...---------------------------$intstr"
         $CONNECT $MyPassword $MyServer $MyUser $MyState $MyTomcatPath  
         echo -e "-----------$MyState Finished $MyUser@$MyServer...------------------\n"
         intstr=$[$intstr+1]
         done
         exec 0<&3
}
 
#   function TomcatLog ()
#   {
#     1.定义一个关于Tomcat错误的数据字典,比如采集的服务器上有30个以上僵死的采集线程,监测每个TOMCAT的日志,
# 制定自动采集重启规则(条件),使之让此脚本真正意
# 义上实现自动化管理.有点理想,也容易:)
#    2...
#   }
 
# --------------------------------------------------------------------------------------------
# Init variables
 
# Host List
FILE="`pwd`/hostlist.txt"
CONNECT="`pwd`/loginssh.exp"
 
MyServer=""
MyUser=""
MyPassword=""
MyTomcatPath=""
MyState=""
 
if [ ! -r $FILE ]; then
echo " Cannot read $FILE or it could not be found!"
exit
fi
 
case "$1" in
state)  
       MyState="CheckState"
       PingHost
       #exit
       if [ "$?" -eq 0 ] ; then
          Login
       else
      echo "Error:Each computer must be ensure that the normal network connection......"
      exit;         
       fi      
 
      ;;  
start)
       MyState="Start"
       PingHost
 
       if [ "$?" -eq 0 ] ; then
          Login
       else
      echo "Error:Each computer must be ensure that the normal network connection......"
      exit;         
       fi       
  
      ;;
stop)  
       MyState="Stop"
       PingHost
       if [ "$?" -eq 0 ] ; then
          Login
       else
      echo "Error:Each computer must be ensure that the normal network connection......."
      exit;         
       fi
      
       ;;   
*)
       echo $"Usage: $0 {start|stop|state}"
       exit
      ;;
esac
exit
=====================================================================================================
发现一个expect的 比较好的说明 比那个所谓的教程好多了
 
一、概述
 
我们通过Shell可以实现简单的控制流功能,如:循环、判断等。但是对于需要交互的场合则必须通过人工来干预,有时候我们可能会需要实现和交互程序如telnet服务器等进行交互的功能。而Expect就使用来实现这种功能的工具。
 
Expect是一个免费的编程工具语言,用来实现自动和交互式任务进行通信,而无需人的干预。Expect的作者Don Libes在1990年开始编写Expect时对Expect做有如下定义:Expect是一个用来实现自动交互功能的软件套件(Expect [is a] software suite for automating interactive tools)。使用它系统管理员的可以创建脚本用来实现对命令或程序提供输入,而这些命令和程序是期望从终端(terminal)得到输入,一般来说这些输入都需要手工输入进行的。Expect则可以根据程序的提示模拟标准输入提供给程序需要的输入来实现交互程序执行。甚至可以实现实现简单的BBS聊天机器人。 :)
 
Expect是不断发展的,随着时间的流逝,其功能越来越强大,已经成为系统管理员的的一个强大助手。Expect需要Tcl编程语言的支持,要在系统上运行Expect必须首先安装Tcl。
 
 
二、Expect工作原理
 
从最简单的层次来说,Expect的工作方式象一个通用化的Chat脚本工具。Chat脚本最早用于UUCP网络内,以用来实现计算机之间需要建立连接时进行特定的登录会话的自动化。
 
Chat脚本由一系列expect-send对组成:expect等待输出中输出特定的字符,通常是一个提示符,然后发送特定的响应。例如下面的Chat 脚本实现等待标准输出出现Login:字符串,然后发送somebody作为用户名;然后等待Password:提示符,并发出响应sillyme。
 
QUOTE:
Login: somebody Password: sillyme
 
这个脚本用来实现一个登录过程,并用特定的用户名和密码实现登录。
 
Expect最简单的脚本操作模式本质上和Chat脚本工作模式是一样的。
 
例子:
1、实现功能
下面我们分析一个响应chsh命令的脚本。我们首先回顾一下这个交互命令的格式。假设我们要为用户chavez改变登录脚本,要求实现的命令交互过程如下:
 
QUOTE:
# chsh chavez
Changing the login shell for chavez
Enter the new value, or press return for the default
Login Shell [/bin/bash]: /bin/tcsh
#
 
可以看到该命令首先输出若干行提示信息并且提示输入用户新的登录shell。我们必须在提示信息后面输入用户的登录shell或者直接回车不修改登录shell。
 
 
2、下面是一个能用来实现自动执行该命令的Expect脚本:
 
CODE:
#!/usr/bin/expect
# Change a login shell to tcsh
 
set user [lindex $argv 0]
spawn chsh $user
expect "]:"
send "/bin/tcsh "
expect eof
exit
 
这个简单的脚本可以解释很多Expect程序的特性。和其他脚本一样首行指定用来执行该脚本的命令程序,这里是/usr/bin/expect。程序第一行用来获得脚本的执行参数(其保存在数组$argv中,从0号开始是参数),并将其保存到变量user中。
 
第二个参数使用Expect的spawn命令来启动脚本和命令的会话,这里启动的是chsh命令,实际上命令是以衍生子进程的方式来运行的。
 
随后的expect和send命令用来实现交互过程。脚本首先等待输出中出现]:字符串,一旦在输出中出现chsh输出到的特征字符串(一般特征字符串往往是等待输入的最后的提示符的特征信息)。对于其他不匹配的信息则会完全忽略。当脚本得到特征字符串时,expect将发送/bin/tcsh和一个回车符给chsh命令。最后脚本等待命令退出(chsh结束),一旦接收到标识子进程已经结束的eof字符,expect脚本也就退出结束。
 
3、决定如何响应
 
管理员往往有这样的需求,希望根据当前的具体情况来以不同的方式对一个命令进行响应。我们可以通过后面的例子看到expect可以实现非常复杂的条件响应,而仅仅通过简单的修改预处理脚本就可以实现。下面的例子是一个更复杂的expect-send例子:
CODE:
expect -re "\[(.*)]:"
if {$expect_out(1,string)!="/bin/tcsh"} {
send "/bin/tcsh" }
send " "
expect eof
 
在这个例子中,第一个expect命令现在使用了-re参数,这个参数表示指定的的字符串是一个正则表达式,而不是一个普通的字符串。对于上面这个例子里是查找一个左方括号字符(其必须进行三次逃逸(escape),因此有三个符号,因为它对于expect和正则表达时来说都是特殊字符)后面跟有零个或多个字符,最后是一个右方括号字符。这里.*表示表示一个或多个任意字符,将其存放在()中是因为将匹配结果存放在一个变量中以实现随后的对匹配结果的访问。
 
当发现一个匹配则检查包含在[]中的字符串,查看是否为/bin/tcsh。如果不是则发送/bin/tcsh给chsh命令作为输入,如果是则仅仅发送一个回车符。这个简单的针对具体情况发出不同相响应的小例子说明了expect的强大功能。
 
在一个正则表达时中,可以在()中包含若干个部分并通过expect_out数组访问它们。各个部分在表达式中从左到右进行编码,从1开始(0包含有整个匹配输出)。()可能会出现嵌套情况,这这种情况下编码从最内层到最外层来进行的。
 
4、使用超时
 
下一个expect例子中将阐述具有超时功能的提示符函数。这个脚本提示用户输入,如果在给定的时间内没有输入,则会超时并返回一个默认的响应。这个脚本接收三个参数:提示符字串,默认响应和超时时间(秒)。
 
CODE:
#!/usr/bin/expect
# Prompt function with timeout and default.
set prompt [lindex $argv 0]
set def [lindex $argv 1]
set response $def
set tout [lindex $argv 2]
 
脚本的第一部分首先是得到运行参数并将其保存到内部变量中。
 
CODE:
send_tty "$prompt: "
set timeout $tout
expect " " {
set raw $expect_out(buffer)
# remove final carriage return
set response [string trimright "$raw" " "]
}
if {"$response" == "} {set response $def}
send "$response "
# Prompt function with timeout and default.
set prompt [lindex $argv 0]
set def [lindex $argv 1]
set response $def
set tout [lindex $argv 2]
 
这是脚本其余的内容。可以看到send_tty命令用来实现在终端上显示提示符字串和一个冒号及空格。set timeout命令设置后面所有的expect命令的等待响应的超时时间为$tout(-l参数用来关闭任何超时设置)。
 
然后expect命令就等待输出中出现回车字符。如果在超时之前得到回车符,那么set命令就会将用户输入的内容赋值给变脸raw。随后的命令将用户输入内容最后的回车符号去除以后赋值给变量response。
 
然后,如果response中内容为空则将response值置为默认值(如果用户在超时以后没有输入或者用户仅仅输入了回车符)。最后send命令将response变量的值加上回车符发送给标准输出。
 
一个有趣的事情是该脚本没有使用spawn命令。 该expect脚本会与任何调用该脚本的进程交互。
 
如果该脚本名为prompt,那么它可以用在任何C风格的shell中。
 
 
% set a='prompt "Enter an answer" silence 10'
Enter an answer: test
 
% echo Answer was "$a"
Answer was test
prompt设定的超时为10秒。如果超时或者用户仅仅输入了回车符号,echo命令将输出
 
Answer was "silence"
 
5、一个更复杂的例子
 
下面我们将讨论一个更加复杂的expect脚本例子,这个脚本使用了一些更复杂的控制结构和很多复杂的交互过程。这个例子用来实现发送write命令给任意的用户,发送的消息来自于一个文件或者来自于键盘输入。
CODE:
#!/usr/bin/expect
# Write to multiple users from a prepared file
# or a message input interactively
 
if {$argc<2} {
send_user "usage: $argv0 file user1 user2 ... "
exit
}
 
send_user命令用来显示使用帮助信息到父进程(一般为用户的shell)的标准输出。
 
CODE:
set nofile 0
# get filename via the Tcl lindex function
set file [lindex $argv 0]
if {$file=="i"} {
set nofile 1
} else {
# make sure message file exists
if {[file isfile $file]!=1} {
send_user "$argv0: file $file not found. "
exit }}
 
这部分实现处理脚本启动参数,其必须是一个储存要发送的消息的文件名或表示使用交互输入得到发送消的内容的"i"命令。
 
变量file被设置为脚本的第一个参数的值,是通过一个Tcl函数lindex来实现的,该函数从列表/数组得到一个特定的元素。[]用来实现将函数lindex的返回值作为set命令的参数。
 
如果脚本的第一个参数是小写的"i",那么变量nofile被设置为1,否则通过调用Tcl的函数isfile来验证参数指定的文件存在,如果不存在就报错退出。
 
可以看到这里使用了if命令来实现逻辑判断功能。该命令后面直接跟判断条件,并且执行在判断条件后的{}内的命令。if条件为false时则运行else后的程序块。
 
CODE:
set procs {}
# start write processes
for {set i 1} {$i<$argc}
{incr i} {
spawn -noecho write
[lindex $argv $i]
lappend procs $spawn_id
}
 
最后一部分使用spawn命令来启动write进程实现向用户发送消息。这里使用了for命令来实现循环控制功能,循环变量首先设置为1,然后因此递增。循环体是最后的{}的内容。这里我们是用脚本的第二个和随后的参数来spawn一个write命令,并将每个参数作为发送消息的用户名。lappend命令使用保存每个spawn的进程的进程ID号的内部变量$spawn_id在变量procs中构造了一个进程ID号列表。
 
CODE:
if {$nofile==0} {
setmesg [open "$file" "r"]
} else {
send_user "enter message,
ending with ^D: " }
 
最后脚本根据变量nofile的值实现打开消息文件或者提示用户输入要发送的消息。
 
CODE:
set timeout -1
while 1 {
if {$nofile==0} {
if {[gets $mesg chars] == -1} break
set line "$chars "
} else {
expect_user {
-re " " {}
eof break }
set line $expect_out(buffer) }
 
foreach spawn_id $procs {
send $line }
sleep 1}
exit
 
上面这段代码说明了实际的消息文本是如何通过无限循环while被发送的。while循环中的 if判断消息是如何得到的。在非交互模式下,下一行内容从消息文件中读出,当文件内容结束时while循环也就结束了。(break命令实现终止循环) 。
 
在交互模式下,expect_user命令从用户接收消息,当用户输入ctrl+D时结束输入,循环同时结束。 两种情况下变量$line都被用来保存下一行消息内容。当是消息文件时,回车会被附加到消息的尾部。
 
foreach循环遍历spawn的所有进程,这些进程的ID号都保存在列表变量$procs中,实现分别和各个进程通信。send命令组成了 foreach的循环体,发送一行消息到当前的write进程。while循环的最后是一个sleep命令,主要是用于处理非交互模式情况下,以确保消息不会太快的发送给各个write进程。当while循环退出时,expect脚本结束。
 
 
三、参考资源
 
Expect软件版本深带有很多例子脚本,不但可以用于学习和理解expect脚本,而且是非常使用的工具。一般可以在 /usr/doc/packages/expect/example看到它们,在某些linux发布中有些expect脚本保存在/usr/bin目录下。
 
Don Libes, Exploring Expect, O'Reilly & Associates, 1995.
 
John Ousterhout, Tcl and the Tk Toolkit, Addison-Wesley, 1994.
 
一些有用的expect脚本
 
autoexpect:这个脚本将根据自身在运行时用户的操作而生成一个expect脚本。它的功能某种程度上类似于在Emacs编辑器的键盘宏工具。一个自动创建的脚本可能是创建自己定制脚本的好的开始。
 
kibitz:这是一个非常有用的工具。通过它两个或更多的用户可以连接到同一个shell进程。
 
tkpasswd: 这个脚本提供了修改用户密码的GUI工具,包括可以检查密码是否是基于字典模式。这个工具同时是一个学习expect和tk的好实例。
============================================================================================================
expect 的和bash共用以及autoexpect简单介绍
 
这里为大家简单介绍一下expect的使用,之前在网上搜索expect的相关资料,意外地发现了autoexpect这个东东,相信会超出你的期望,至少对我是这样。
 
春节放假的时候,被要求修改部门所有主机的密码,我们大概有100多台主机,如果一台台上去改,实在是太累也没有任何成就感,因此想到用expect来解决问题:
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#!/usr/bin/expect   --
set timeout -1
 
if { [llength $argv] < 3} {
   puts "usage: $argv0 ip oldpasswd newpasswd"
   exit 1
}
 
spawn /usr/local/bin/ssh [lindex $argv 0] -p36000 -lroot
 
expect "assword: "
send "[lindex $argv 1]\r"
expect "#"
send "passwd\r"
expect "New password:"
send "[lindex $argv 2]\r"
expect "Re-enter new password:"
send "[lindex $argv 2]\r"
expect "#" {send "exit\r"}
expect eof
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
简单解释一下:expect是基于TCL脚本的一种脚本语言,可以使需要人工交互的一些工作自动化。expect中最重要的几个关键字就是spawn, expect, send, lindex 。
 
spawn 用于生成一个子进程运行命令
expect 用于期待一个字符串的出现
send 就是模拟人工输入一个字符串
lindex 可以在数组中选择某个元素
 
上面的脚本基本上就是创建一个ssh进程,尝试登录到指定IP,当ssh提示输入密码时,把oldpasswd发送过去,然后期待提示符"#"的出现,以此类推就可以完成所有工作。
把上面脚本保存到chg_pwd.exp,然后加上可执行权限,就可以用./chg_pwd.exp 172.16.24.57 old new修改密码了,然后写个shell脚本就可以批量修改密码了,如:
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#!/bin/bash
 
filename=$1
while read internalip
do
   ./chg_pwd.exp $internalip $2 $3
done < $filename
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
一种语言再简单,也需要学习。然而autoexpect甚至可以自动生成expect脚本。
 
运行autoexpect -p就进入autoexpect创建的shell中,然后输入的命令交互都被记录下来,最后输入exit退出,expect脚本被保存在script.exp中。
下面的例子演示了通过ssh登录到一台主机然后退出。
 
rong@dev:~$ autoexpect -p ssh 172.16.24.57 -lroot -p36000
autoexpect started, file is script.exp
root's password:
Last login: Thu Feb 24 2005 20:00:32 +0800
Linux 2.4.20.
You have mail.
root@platform57:~# exit
logout
autoexpect done, file is script.exp
rong@dev:~$
 
只要再次执行 ./script.exp,就可以重复刚才运行的命令。
 
效果就象先录音然后回放一样。
 
如果对expect还不熟悉,我极力推荐去了解一下。他可以帮你自动完成很多任务,甚至可以应用于自动测试等等