最近项目需求,须要写一些shell脚本交互,管道不够用时,expect能够很好的实现脚本之间交互,搜索资料,发现网上好多文章都是转载的,以为这篇文章还不错,因此简单修改以后拿过来和你们分享一下~shell
1. expect是spawn: 后面加上须要执行的shell命令,好比说spawn sudo touch testfile
1.3 expect: 只有spawn执行的命令结果才会被expect捕捉到,由于spawn会启动一个进程,只有这个进程的相关信息才会被捕捉到,主要包括:标准输入的提示信息,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 exit
9 }
10
11 set user [lindex $argv [$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 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行: 测试,无需加-S亦可;
第15行:通常状况下,若是连续作两个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"
9 expect "*$"
这个例子其实是经过ssh去登陆远程机器,而且在远程机器上创佳一个目录,咱们看到在咱们输入密码后并无去expect eof,这是由于ssh这个spawn并无结束,并且手动操做时ssh实际上也不会本身结束除非你exit;因此你只能expect bash的提示符,固然也能够是机器名等,这样才能够在远程建立一个目录。
注意,请不要用spawn mkdir tmpdir,这样会使得上一个spawn即ssh结束,那么你的tmpdir将在本机创建。
固然实际状况下可能会要你确认ssh key,能够经过并行的expect进行处理,很少赘述。
5. 以为bash不少状况下已经很强大,因此可能用expect只须要掌握这些就行了,其余的若是用到能够再去google了。bash
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 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: 23: expect eof
注意代码刚开始的第一行,指定了expect的路径,与shell脚本相同,这一句指定了程序在执行时到哪里去寻找相应的启动程序。代码刚开始还设定了timeout的时间为10秒,若是在执行scp任务时遇到了代码中没有指定的异常,则在等待10秒后该脚本的执行会自动终止。ssh
spawn表明在本地终端执行的语句,在该语句开始执行后,expect开始捕获终端的输出信息,而后作出对应的操做。expect代码中的捕获的(yes/no)内容用于完成第一次访问目标主机时保存密钥的操做。有了这一句,scp的任务减小了中断的状况。代码结尾的expect eof与spawn对应,表示捕获终端输出信息的终止。post
有了这段expect的代码,还只能完成对单个远程主机的scp任务。若是须要 实现批量scp的任务,则须要再写一个shell脚原本调用这个expect脚本。测试
1: #!/bin/shui
2:google
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、用户名、密码,这些信息须要写成如下的格式:spa
IP username password.net
中间用空格或tab键来分隔,多台主机的信息须要写多行内容。
这样就指定了两台远程主机的信息。注意,若是远程主机密码中有“$”、“#”这类特殊字符的话,在编写列表文件时就须要在这些特殊字符前加上转义字符,不然expect在执行时会输入错误的密码。
对于这个shell脚本,保存为batch_scp.sh文件,与刚才保存的expect_scp文件和列表文件(就定义为hosts.list文件吧)放到同一目录下,执行时按照如下方式输入命令就能够了:
./batch_scp.sh ./hosts.list /root/src_file /root/destfile
===============================================================================
下面咱们来看一些expect的一些内部参数:
exp_continue [-continue_timer]
The command exp_continue allows expect itself to continue executing rather than returning as it normally would. By default exp_continue resets the timeout timer. The -continue_timer flag prevents timer from being restarted.
exp_version [[-exit] version]
is useful for assuring that the script is compatible with the current version of Expect.
With no arguments, the current version of Expect is returned. This version may then be encoded in your script. If you actually know that you are not using features of recent versions, you can specify an earlier version.
具体的用法还能够查看文档~
#!/bin/sh
# \
exec expect -- "$0" ${1+"$@"}
exp_version -exit 5.0
if {$argc!=2} {
send_user "usage: remote-exec command password\n"
send_user "Eg. remote-exec \"ssh user@host ls\; echo done\" password\n"
send_user "or: remote-exec \"scp /local-file user@host:/remote-file\" password\n"
send_user "or: remote-exec \"scp user@host:/remote-file local-file\" password\n"
send_user "or: remote-exec \"rsync --rsh=ssh /local-file user@host:/remote-file\" password\n"
send_user "Caution: command should be quoted.\n"
exit
}
set cmd [lindex $argv 0]
set password [lindex $argv 1]
eval spawn $cmd
set timeout 600
while {1} {
expect -re "Are you sure you want to continue connecting (yes/no)?" {
# First connect, no public key in ~/.ssh/known_hosts
send "yes\r"
} -re "assword:" {
# Already has public key in ~/.ssh/known_hosts
send "$password\r"
} -re "Permission denied, please try again." {
# Password not correct
exit
} -re "kB/s|MB/s" {
# User equivalence already established, no password is necessary
set timeout -1
} -re "file list ..." {
# rsync started
set timeout -1
} -re "bind: Address already in use" {
# For local or remote port forwarding
set timeout -1
} -re "Is a directory|No such file or directory" {
exit
} -re "Connection refused" {
exit
} timeout {
exit
} eof {
exit
}
}
注意用法:
Eg. remote-exec "ssh user@host ls; echo done" password
or: remote-exec "scp /local-file user@host:/remote-file" password
or: remote-exec "scp user@host:/remote-file local-file" password
or: remote-exec "rsync --rsh=ssh /local-file user@host:/remote-file" password
Caution: command should be quoted.