expect学习笔记及实例详解

您所在的位置:网站首页 invalid记忆方法 expect学习笔记及实例详解

expect学习笔记及实例详解

2024-07-14 08:10| 来源: 网络整理| 查看: 265

引用自: http://wenku.baidu.com/view/b65e103610661ed9ad51f374.html 1. expect 是基于tcl 演变而来的,所以很多语法和tcl 类似,基本的语法如下 所示: 1.1 首行加上/usr/bin/expect 1.2 spawn: 后面加上需要执行的shell 命令,比如说spawn sudo touch testfile 1.3 expect: 只有spawn 执行的命令结果才会被expect 捕捉到,因为spawn 会启 动一个进程,只有这个进程的相关信息才会被捕捉到,主要包括:标准输入的提 示信息,eof 和timeout。 1.4 send 和send_user:send 会将expect 脚本中需要的信息发送给spawn 启动 的那个进程,而send_user 只是回显用户发出的信息,类似于shell 中的echo 而 已。 2. 一个小例子,用于linux 下账户的建立: filename: account.sh,可以使用./account.sh newaccout 来执行; 1 #!/usr/bin/expect 2 3 set passwd "mypasswd" 4 set timeout 60 5 6 if {$argc != 1} { 7 send "usage ./account.sh \$newaccount\n" 8 exit 9 } 10 11 set user [lindex $argv [expr $argc-1]] 12 13 spawn sudo useradd -s /bin/bash -g mygroup -m $user 14 15 expect { 16 "assword" { 17 send_user "sudo now\n" 18 send "$passwd\n" 19 exp_continue 20 } 21 eof 22 { 23 send_user "eof\n" 24 } 25 } 26 27 spawn sudo passwd $user 28 expect { 29 "assword" { 30 send "$passwd\n" 31 exp_continue 32 } 33 eof 34 { 35 send_user "eof" 36 } 37 } 38 39 spawn sudo smbpasswd -a $user 40 expect { 41 "assword" { 42 send "$passwd\n" 43 exp_continue 44 } 45 eof 46 { 47 send_user "eof" 48 } 49 } 3. 注意点: 第3 行: 对变量赋值的方法; 第4 行: 默认情况下,timeout 是10 秒; 第6 行: 参数的数目可以用$argc 得到; 第11 行:参数存在$argv 当中,比如取第一个参数就是[lindex $argv 0];并且 如果需要计算的话必须用expr,如计算2-1,则必须用[expr 2-1]; 第13 行:用spawn 来执行一条shell 命令,shell 命令根据具体情况可自行调整; 有文章说sudo 要加-S,经过实际测试,无需加-S 亦可; 第15 行:一般情况下,如果连续做两个expect,那么实际上是串行执行的,用。expect 与“{ ”之间直接必须有空格或则TAB间隔,否则会出麻烦,会报错 invalid command name "expect{"  例子中的结构则是并行执行的,主要是看匹配到了哪一个;在这个例子中,如果 你写成串行的话,即 expect "assword" send "$passwd\n" expect eof send_user "eof" 那么第一次将会正确运行,因为第一次sudo 时需要密码;但是第二次运行时由于 密码已经输过(默认情况下sudo 密码再次输入时间为5 分钟),则不会提示用户 去输入,所以第一个expect 将无法匹配到assword,而且必须注意的是如果是 spawn 命令出现交互式提问的但是expect 匹配不上的话,那么程序会按照timeout 的设置进行等待;可是如果spawn 直接发出了eof 也就是本例的情况,那么expect "assword"将不会等待,而直接去执行expect eof。 这时就会报expect: spawn id exp6 not open,因为没有spawn 在执行,后面的 expect 脚本也将会因为这个原因而不再执行;所以对于类似sudo 这种命令分支 不定的情况,最好是使用并行的方式进行处理; 第17 行:仅仅是一个用户提示而已,可以删除; 第18 行:向spawn 进程发送password; 第19 行:使得spawn 进程在匹配到一个后再去匹配接下来的交互提示; 第21 行:eof 是必须去匹配的,在spawn 进程结束后会向expect 发送eof;如果 不去匹配,有时也能运行,比如sleep 多少秒后再去spawn 下一个命令,但是不 要依赖这种行为,很有可能今天还可以,明天就不能用了; 4. 其他 下面这个例子比较特殊,在整个过程中就不能expect eof 了: 1 #!/usr/bin/expect 2 3 set timeout 30 4 spawn ssh 10.192.224.224 5 expect "password:" 6 send "mypassword\n" 7 expect "*$" 8 send "mkdir tmpdir\n" #远程执行命令用send发送,不用spawn 9 expect "*$" #注意这个地方,要与操作系统上环境变量PS1相匹配,尤其是有PS1有空格的情况下,一定在expct "*$ "把空格加上,加不上你就完蛋了。我试过。 这个例子实际上是通过ssh 去登录远程机器,并且在远程机器上创佳一个目录, 我们看到在我们输入密码后并没有去expect eof,这是因为ssh 这个spawn 并没 有结束,而且手动操作时ssh 实际上也不会自己结束除非你exit;所以你只能 expect bash 的提示符,当然也可以是机器名等,这样才可以在远程创建一个目 录。 注意,请不要用spawn mkdir tmpdir,这样会使得上一个spawn 即ssh 结束,那 么你的tmpdir 将在本机建立。 当然实际情况下可能会要你确认ssh key,可以通过并行的expect 进行处理,不 多赘述。 5. 觉得bash 很多情况下已经很强大,所以可能用expect 只需要掌握这些就好了, 其他的如果用到可以再去google 了。 源代码图片:

6 \实例:下面这个脚本是完成对单个服务器scp任务。

1: #!/usr/bin/expect 2: 3: set timeout 10 4: set host [lindex $argv 0] 5: set username [lindex $argv 1] 6: set password [lindex $argv 2] 7: set src_file [lindex $argv 3] 8: set dest_file [lindex $argv 4] 9: 10: spawn scp $src_file $username@$host:$dest_file 11: expect { 12: "(yes/no)?" 13: { 14: send "yes\n" 15: expect "*assword:" { send "$password\n"} 16: } 17: "*assword:" 18: { 19: send "$password\n" 20: } 21: } 22: expect "100%" 23: expect eof 参考源代码图片:

注意代码刚开始的第一行,指定了expect的路径,与shell脚本相同,这一句指定了程序在执行时到哪里去寻找相应的启动程序。代码刚开始还设定了timeout的时间为10秒,如果在执行scp任务时遇到了代码中没有指定的异常,则在等待10秒后该脚本的执行会自动终止。

spawn代表在本地终端执行的语句,在该语句开始执行后,expect开始捕获终端的输出信息,然后做出对应的操作。expect代码中的捕获的(yes/no)内容用于完成第一次访问目标主机时保存密钥的操作。有了这一句,scp的任务减少了中断的情况。代码结尾的expect eof与spawn对应,表示捕获终端输出信息的终止。

 

有了这段expect的代码,还只能完成对单个远程主机的scp任务。如果需要实现批量scp的任务,则需要再写一个shell脚本来调用这个expect脚本。

1: #!/bin/sh

2: 3: list_file=$1 4: src_file=$2 5: dest_file=$3 6: 7: cat $list_file | while read line 8: do 9: host_ip=`echo $line | awk '{print $1}'` 10: username=`echo $line | awk '{print $2}'` 11: password=`echo $line | awk '{print $3}'` 12: echo "$host_ip" 13: ./expect_scp $host_ip $username $password $src_file $dest_file 15: done 参考代码图片如下:

很简单的代码,指定了3个参数:列表文件的位置、本地源文件路径、远程主机目标文件路径。需要说明的是其中的列表文件指定了远程主机ip、用户名、密码,这些信息需要写成以下的格式:

IP username password

中间用空格或tab键来分隔,多台主机的信息需要写多行内容。

这样就指定了两台远程主机的信息。注意,如果远程主机密码中有“$”、“#”这类特殊字符的话,在编写列表文件时就需要在这些特殊字符前加上转义字符,否则expect在执行时会输入错误的密码。

对于这个shell脚本,保存为batch_scp.sh文件,与刚才保存的expect_scp文件和列表文件(就定义为hosts.list文件吧)放到同一目录下,执行时按照以下方式输入命令就可以了:

1.jpg

2.jpg

3.jpg

1.jpg

2.jpg

11.jpg

12.jpg

13.jpg

14.jpg

再记几个实用心得,以备不时之需

1. 循环式匹配: exp_continue

expect 的匹配可以看做是一个循环,通常匹配之后都会退出语句,但如果有 exp_continue 则可以不断循环匹配,输入多条命令

expect{ "$passprompt" { send "$password"; exp_continue } "$prompt" {send "$c\r"} }

在这个例子里,遇到密码提示的时候,送出密码,然后继续 expect,遇到命令提示,送出命令,然后退出 expect,当然,可以做得更复杂,一条一条送命令,送没了再退出。这样,expect 的结构会比较漂亮,而且容易扩展。

2. 正则匹配: -re

作为一种 tcl 的分支,expect 支持 tcl 的正则表达式,正则表达式这里就不多说了,正则匹配的一个重要用途是,对多个匹配关键字进行相同的操作,比如

expect { -re "$prompt|$rootprompt" { send "$c\r" } }

这里是对普通用户的提示和root用户的提示进行同样的操作。

3. 利用PS1环境变量

在 expect 里,根据程序的返回状态做操作不是件容易事,很多做法都不是十分干净,我的一个方法是,根据 $? 的值设置 PS1 环境变量,这样,下一次出现的 Shell 提示就不是之前的提示了,只要特别 expect 这个提示,进行操作就行了。

嗯,这算是个野路子,不正规哈,欢迎指正。

4. 抓取远程的输出到本地:expect_out 和 full_buffer

我们经常需要抓取远程的输出,这时,可以借助 expect_out 来抓取两次 expect 之间的内容,它有两个用法:

expect_out(buffer) 这个直接抓取两次 expect 之间的全部 bufferexpect_out(1,buffer) 这个抓取正则匹配的部分

后者这里不多说了,前者有个问题,就是当两条命令之间输出很多时,可能 buffer 会满,在这种情况下,expect_out(buffer) 不是全部的 buffer 内容,而是最后的,这时要靠 full_buffer 帮助了,这里举个例子

set result "" expect { "$prompt" { append result $expect_out(buffer) puts stderr $result set result "" send "$newcmd" exp_continue } full_buffer { append result $expect_out(buffer) exp_continue } }

这时个示例,注意 full_buffer 是特殊匹配事件,类似 timeout,不是字符串。

5. 脚本之间的调用

1). fork  ( /directory/script.sh) :如果shell中包含执行命令,那么子命令并不影响父级的命令,在子命令执行完后再执行父级命令。子级的环境变量不会影响到父级。 

fork是最普通的, 就是直接在脚本里面用/directory/script.sh来调用script.sh这个脚本.

运行的时候开一个sub-shell执行调用的脚本,sub-shell执行的时候, parent-shell还在。

sub-shell执行完毕后返回parent-shell. sub-shell从parent-shell继承环境变量.但是sub-shell中的环境变量不会带回parent-shell

2). exec (exec /directory/script.sh):执行子级的命令后,不再执行父级命令。

exec与fork不同,并不启动新的shell,而是用要被执行命令替换当前的shell进程,并且将老进程的环境清理掉,而且exec命令后的其它命令将不再执行,所以调用脚本的终端打印不会显示在shell里, 这是exec和source的区别

3). source (source /directory/script.sh):执行子级命令后继续执行父级命令,同时子级设置的环境变量会影响到父级的环境变量。

与fork的区别是不新开一个sub-shell来执行被调用的脚本,而是在同一个shell中执行. 所以被调用的脚本中声明的变量和环境变量, 都可以在主脚本中得到和使用.



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3