Linux下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。
  引⽤:Login: somebody Password: sillyme
  这个脚本⽤来实现⼀个登录过程,并⽤特定的⽤户名和密码实现登录。
  Expect最简单的脚本操作模式本质上和Chat脚本⼯作模式是⼀样的。
  例⼦:
  1、实现功能
  下⾯我们分析⼀个响应chsh命令的脚本。我们⾸先回顾⼀下这个交互命令的格式。假设我们要为⽤户chavez改变登录脚本,要求实现的命令交互过程如下:
  引⽤:# 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脚本:
  #!/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例⼦:
  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例⼦中将阐述具有超时功能的提⽰符函数。这个脚本提⽰⽤户输⼊,如果在给定的时间内没有输⼊,则会超时并返回⼀个默认的响应。这个脚本接收三个参数:提⽰符字串,默认响应和超时时间(秒)。
  #!/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]
  脚本的第⼀部分⾸先是得到运⾏参数并将其保存到内部变量中。
  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
  这是脚本其余的内容。可以看到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命令给任意的⽤户,发送的消息来⾃于⼀个⽂件或者来⾃于键盘输⼊。
  #!/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)的标准输出。
  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后的程序块。
  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号列表。
  if {$nofile==0} {
  setmesg [open "$file" "r"]
  } else {
  send_user "enter message,
  ending with ^D: " }
  最后脚本根据变量nofile的值实现打开消息⽂件或者提⽰⽤户输⼊要发送的消息。
  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) }
linux循环执行命令脚本
  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脚本结束。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。