expect

shell脚本实现ssh自动登陆远程服务器示例:linux

 

#!/usr/bin/expect正则表达式

spawn ssh root@192.168.22.194shell

expect "*password:"bash

send "123\r"服务器

expect "*#"ssh

interact学习

 

 

Expect是一个用来处理交互的命令。借助Expect,咱们能够将交互过程写在一个脚本上,使之自动化完成。形象的说,ssh登陆,ftp登陆等都符合交互的定义。下文咱们首先提出一个问题,而后介绍基础知四个命令,最后提出解决方法。测试

问题google

如何从机器A上ssh到机器B上,而后执行机器B上的命令?如何使之自动化完成?


四个命令

Expect中最关键的四个命令是send,expect,spawn,interact。

send:用于向进程发送字符串

expect:从进程接收字符串

spawn:启动新的进程

interact:容许用户交互

1. send命令

send命令接收一个字符串参数,并将该参数发送到进程。

expect1.1> send "hello world\n"

hello world

2. expect命令


(1)基础知识

expect命令和send命令正好相反,expect一般是用来等待一个进程的反馈。expect能够接收一个字符串参数,也能够接收正则表达式参数。和上文的send命令结合,如今咱们能够看一个最简单的交互式的例子:

expect "hi\n"

send "hello there!\n"

这两行代码的意思是:从标准输入中等到hi和换行键后,向标准输出输出hello there。

tips: $expect_out(buffer)存储了全部对expect的输入,<$expect_out(0,string)>存储了匹配到expect参数的输入。

好比以下程序:

expect "hi\n"

send "you typed <$expect_out(buffer)>"

send "but I only expected <$expect_out(0,string)>"

当在标准输入中输入

test

hi

是,运行结果以下

you typed: test

hi

I only expect: hi


(2)模式-动做

expect最经常使用的语法是来自tcl语言的模式-动做。这种语法极其灵活,下面咱们就各类语法分别说明。

单一分支模式语法:

expect "hi" {send "You said hi"}

匹配到hi后,会输出"you said hi"

多分支模式语法:

expect "hi" { send "You said hi\n" } \

"hello" { send "Hello yourself\n" } \

"bye" { send "That was unexpected\n" }

匹配到hi,hello,bye任意一个字符串时,执行相应的输出。等同于以下写法:

expect {

"hi" { send "You said hi\n"}

"hello" { send "Hello yourself\n"}

"bye" { send "That was unexpected\n"}

}


3. spawn命令

上文的全部demo都是和标准输入输出进行交互,可是咱们跟但愿他能够和某一个进程进行交互。spawm命令就是用来启动新的进程的。spawn后的send和expect命令都是和spawn打开的进程进行交互的。结合上文的send和expect命令咱们能够看一下更复杂的程序段了。

set timeout -1

spawn ftp ftp.test.com      //打开新的进程,该进程用户链接远程ftp服务器

expect "Name"             //进程返回Name时

send "user\r"        //向进程输入anonymous\r

expect "Password:"        //进程返回Password:时

send "123456\r"    //向进程输入don@libes.com\r

expect "ftp> "            //进程返回ftp>时

send "binary\r"           //向进程输入binary\r

expect "ftp> "            //进程返回ftp>时

send "get test.tar.gz\r"  //向进程输入get test.tar.gz\r

这段代码的做用是登陆到ftp服务器ftp ftp.uu.net上,并以二进制的方式下载服务器上的文件test.tar.gz。程序中有详细的注释。


4.interact

到如今为止,咱们已经能够结合spawn、expect、send自动化的完成不少任务了。可是,如何让人在适当的时候干预这个过程了。好比下载完ftp文件时,仍然能够停留在ftp命令行状态,以便手动的执行后续命令。interact能够达到这些目的。下面的demo在自动登陆ftp后,容许用户交互。

spawn ftp ftp.test.com

expect "Name"

send "user\r"

expect "Password:"

send "123456\r"

interact


解决方法

上文中提到:

如何从机器A上ssh到机器B上,而后执行机器B上的命令?如何使之自动化完成?

下面一段脚本实现了从机器A登陆到机器B,而后执行机器B上的pwd命令,并停留在B机器上,等待用户交互。具体含义请参考上文。

#!/home/tools/bin/64/expect -f

 set timeout -1 

 spawn ssh $BUser@$BHost

 expect  "*password:" { send "$password\r" }

 expect  "$*" { send "pwd\r" }

 interact

 

 

 

 

expect学习笔记及实例详解

 

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

相关文章
相关标签/搜索