您的位置:首页 > 运维架构 > Shell

使用expect实现shell自动交互

2015-01-18 15:26 633 查看
安装expect

1.首先确认expect的包要安置。

#rpm -qa | grep expect

如果没有则需要下载安装,

#yum install expect

2.安装完成后,查看expect的路径,可以用

#which expect

/usr/bin/expect

二 expect基本的语法如下所示:

2.1 首行加上/usr/bin/expect

2.2 spawn: 后面加上需要执行的shell命令,比如说spawn sudo touch testfile

2.3 expect: 只有spawn执行的命令结果才会被expect捕捉到,因为spawn会启动一个进程,只有这个进程的相关信息才会被捕捉到,主要包括:标准输入的提示信息,eof和timeout。

2.4 send和send_user:send会将expect脚本中需要的信息发送给spawn启动的那个进程,而send_user只是回显用户发出的信息,类似于shell中的echo而已。

三、使用expect脚本的小窍门


1、使用“-c”选项,从命令行执行expect脚本

expect可以让你使用“-c”选项,直接在命令行中执行它,如下所示:

[plain] view
plaincopy





$ expect -c 'expect "\n" {send "pressed enter\n"}

pressed enter

$

如果你执行了上面的脚本,它会等待输入换行符(\n)。按“enter”键以后,它会打印出“pressed enter”这个消息,然后退出。


2、使用“-i”选项交互地执行expect脚本

使用“-i”选项,可以通过来自于标准输入的读命令来交互地执行expect脚本。如下所示:

[plain] view
plaincopy





$ expect -i arg1 arg2 arg3

expect1.1>set argv

arg1 arg2 arg3

expect1.2>

正常情况下,当你执行上面的expect命令的时候(没有“-i”选项),它会把arg1当成脚本的文件名,所以“-i”选项可以让脚本把多个参数当成一个连续的列表。

当你执行带有“-c”选项的expect脚本的时候,这个选项是十分有用的。因为默认情况下,expect是交互地执行的。


3、当执行expect脚本的时候,输出调试信息

当你用“-d”选项执行代码的时候,你可以输出诊断的信息。如下所示:

[plain] view
plaincopy





$ cat sample.exp

# !/usr/bin/expect -fexpect "\n";send "pressed enter";$ expect -d sample.expexpect version 5.43.0argv[0] = expect argv[1] = -d argv[2] = sample.expset argc 0set argv0 "sample.exp"set argv ""executing commands from command file sample.exp

expect: does "" (spawn_id exp0) match glob pattern "\n"? no

expect: does "\n" (spawn_id exp0) match glob pattern "\n"? yes

expect: set expect_out(0,string) "\n"

expect: set expect_out(spawn_id) "exp0"

expect: set expect_out(buffer) "\n"

send: sending "pressed enter" to { exp0 pressed enter}


4、使用“-D”选项启动expect调试器

“-D”选项用于启动调试器,它只接受一个布尔值的参数。这个参数表示提示器必须马上启动,还是只是初始化调试器,以后再使用它。

[plain] view
plaincopy





$ expect -D 1 script

“-D”选项左边的选项会在调试器启动以前被处理。然后,在调试器启动以后,剩下的命令才会被执行。

[plain] view
plaincopy





$ expect -c 'set timeout 10' -D 1 -c 'set a 1'

1: set a 1

dbg1.0>


5、逐行地执行expect脚本

通常,expect会在执行脚本之前,把整个脚本都读入到内存中。“-b”选项可以让expect一次只读取脚本中的一行。当你没有写完整个脚本的时候,这是十分有用的,expect可以开始执行这个不完整的脚本,并且,它可以避免把脚本写入到临时文件中。

[plain] view
plaincopy





$ expect -b


6、让expect不解释命令行参数

你可以使用标识符让expect不解释命令行参数。

你可以像下面这样的读入命令行参数:

[plain] view
plaincopy





$ cat print_cmdline_args.exp

#!/usr/bin/expect

puts 'argv0 : [lindex $argv 0]';

puts 'argv1 : [lindex $argv 1]';

当执行上面的脚本的时候,会跳过命令行选项,它们会被当成参数(而不是expect选项),如下所示:

[plain] view
plaincopy





$ expect print_cmdline_args.exp -d -c

argv0 : -d

argv1 : -c



四、expect简单例子

为了更好理解except脚本几个简单参数,我们再举一个简单的例子:

[plain] view
plaincopy





#!/usr/bin/expect

set timeout 30

spawn ssh -l username 192.168.1.1

expect "password:"

send "ispass\r"

interact

说明:

1. [#!/usr/bin/expect]

这一行告诉操作系统脚本里的代码使用那一个shell来执行。这里的expect其实和linux下的bash、windows下的cmd是一类东西。

注意:这一行需要在脚本的第一行。

2. [set timeout 30]

基本上认识英文的都知道这是设置超时时间的,现在你只要记住他的计时单位是:秒

3. [spawn ssh -l username 192.168.1.1]

spawn是进入expect环境后才可以执行的expect内部命令,如果没有装expect或者直接在默认的SHELL下执行是找不到spawn命令的。所以不要用 “which spawn“之类的命令去找spawn命令。好比windows里的dir就是一个内部命令,这个命令由shell自带,你无法找到一个dir.com 或 dir.exe 的可执行文件。

它主要的功能是给ssh运行进程加个壳,用来传递交互指令。

4. [expect "password:"]

这里的expect也是expect的一个内部命令,有点晕吧,expect的shell命令和内部命令是一样的,但不是一个功能,习惯就好了。这个命令的意思是判断上次输出结果里是否包含“password:”的字符串,如果有则立即返回,否则就等待一段时间后返回,这里等待时长就是前面设置的30秒

5. [send "ispass\r"]

这里就是执行交互动作,与手工输入密码的动作等效。

温馨提示: 命令字符串结尾别忘记加上 “\r”,如果出现异常等待的状态可以核查一下。

6. [interact]

执行完成后保持交互状态,把控制权交给控制台,这个时候就可以手工操作了。如果没有这一句登录完成后会退出,而不是留在远程终端上。如果你只是登录过去执行一段命令就退出,可改为[expect eof]



五、expect实用案例


1、expect实现ssh无密钥登陆

说明:用了两个脚本,一个bash脚本(send_key.sh),在其中调用另外一个expect脚本(scp_key_to_node.exp),两个脚本放在同一个目录下:

(1)bash脚本:send_key.sh

[plain] view
plaincopy





#!/bin/bash

ssh-keygen -t dsa

for (( i = 1; i <= 100 ; i ++ ))

do

./scp_key_to_node.exp $i

done

(2)expect脚本:(scp_key_to_node.exp)

[plain] view
plaincopy





#!/usr/bin/expect

set timeout 5

set hostno [lindex $argv 0]

spawn scp ~/.ssh/id_dsa.pub impala$hostno:~/.ssh/pub_key

expect "*password*"

send "111111\r"

spawn ssh impala$hostno "cat ~/.ssh/pub_key/ >> ~/.ssh/authorized_keys"

expect "*password*"

send "111111\r"

spawn ssh impala$hostno "chmod 600 ~/.ssh/authorized_keys"

expect "*password*"

send "111111\r"

expect eof

(3)分析:

set可以设置超时,或者设置一个变量的值

spawn是执行一个命令

expect等待一个匹配的输出流中的内容

send是匹配到之后向输入流写入的内容

[lindex $argv 0]表示脚本的第0个参数

expect eof表示读取到文件结束符

(4)脚本执行方式:

在脚本所在的目录下执行:

# ./send_key.sh


2、ssh实现自动登录,并停在登录服务器上

[plain] view
plaincopy





#!/usr/bin/expect -f

set ip [lindex $argv 0 ] //接收第一个参数,并设置IP

set password [lindex $argv 1 ] //接收第二个参数,并设置密码

set timeout 10 //设置超时时间

spawn ssh root@$ip //发送ssh请滶

expect { //返回信息匹配

"*yes/no" { send "yes\r"; exp_continue} //第一次ssh连接会提示yes/no,继续

"*password:" { send "$password\r" } //出现密码提示,发送密码

}

interact //交互模式,用户会停留在远程服务器上面.

运行结果如下:

[plain] view
plaincopy





root@ubuntu:/home/zhangy# ./test.exp 192.168.1.130 admin

spawn ssh root@192.168.1.130

Last login: Fri Sep 7 10:47:43 2012 from 192.168.1.142

[root@linux ~]#



3、根据IP和密码连接到不同的机器.

[plain] view
plaincopy





#!/usr/bin/expect -f

set ip 192.168.1.130

set password admin

set timeout 10

spawn ssh root@$ip

expect {

"*yes/no" { send "yes\r"; exp_continue}

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

}

运行结果如下:

[plain] view
plaincopy





root@ubuntu:/home/zhangy# ./web.exp

spawn ssh root@192.168.1.130

Last login: Fri Sep 7 12:59:02 2012 from 192.168.1.142



4、远程登录到服务器,并且执行命令,执行完后并退出

[plain] view
plaincopy





#!/usr/bin/expect -f

set ip 192.168.1.130

set password admin

set timeout 10

spawn ssh root@$ip

expect {

"*yes/no" { send "yes\r"; exp_continue}

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

}

expect "#*"

send "pwd\r"

send "exit\r"

expect eof

运行结果如下:

[plain] view
plaincopy





root@ubuntu:/home/zhangy# ./test3.exp

spawn ssh root@192.168.1.130

root@192.168.1.130's password:

Last login: Fri Sep 7 14:05:07 2012 from 116.246.27.90

[root@localhost ~]# pwd

/root

[root@localhost ~]# exit

logout

Connection to 192.168.1.130 closed.


5、远程登录到ftp,并且下载文件

[plain] view
plaincopy





#!/usr/bin/expect -f

set ip [lindex $argv 0 ]

set dir [lindex $argv 1 ]

set file [lindex $argv 2 ]

set timeout 10

spawn ftp $ip

expect "Name*"

send "zwh\r"

expect "Password:*"

send "zwh\r"

expect "ftp>*"

send "lcd $dir\r"

expect {

"*file" { send_user "local $_dir No such file or directory";send "quit\r" }

"*now*" { send "get $dir/$file $dir/$file\r"}

}

expect {

"*Failed" { send_user "remote $file No such file";send "quit\r" }

"*OK" { send_user "$file has been download\r";send "quit\r"}

}

expect eof

运行结果如下:

[plain] view
plaincopy





root@ubuntu:/home/zhangy# ./test2.exp 192.168.1.130 /var/www/www aaa.html

spawn ftp 192.168.1.130

Connected to 192.168.1.130.

220 (vsFTPd 2.0.5)

Name (192.168.1.130:root): zwh

331 Please specify the password.

Password:

230 Login successful.

Remote system type is UNIX.

Using binary mode to transfer files.

ftp> lcd /var/www/www

Local directory now /var/www/www

ftp> get /var/www/www/aaa.html /var/www/www/aaa.html

local: /var/www/www/aaa.html remote: /var/www/www/aaa.html

200 PORT command successful. Consider using PASV.

150 Opening BINARY mode data connection for /var/www/www/aaa.html (66 bytes).

226 File send OK.

66 bytes received in 0.00 secs (515.6 kB/s)

quit aaa.html has been download

221 Goodbye.


6、使用expect调用passwd自动更改密码

[plain] view
plaincopy





#!/bin/bash

USER=mynameuser

PASS=oldpassword

NPASS=newpassword

expect << EOF

spawn passwd

expect "Changing password for ${USER}."

send "${PASS}\r"

expect "Enter new UNIX password:"

send "${NPASS}\r"

expect "Retype new UNIX password:"

send "${NPASS}\r"

expect eof;

EOF


7、完成对服务器的scp任务:

[plain] view
plaincopy





#!/usr/bin/expect

set timeout 10

set host [lindex $argv 0]

set username [lindex $argv 1]

set password [lindex $argv 2]

set src_file [lindex $argv 3]

set dest_file [lindex $argv 4]

spawn scp $src_file $username@$host:$dest_file

expect {

"(yes/no)?"

{

send "yes\n"

expect "*assword:" { send "$password\n"}

}

"*assword:"

{

send "$password\n"

}

}

expect "100%"

expect eof

说明:

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

(2)这个脚本设置了5个需要手动输入的参数,分别为:目标主机的IP、用户名、密码、本地文件路径、目标主机中的文件路径。如果将以上脚本保存为expect_scp文件,则在shell下执行时需要按以下的规范来输入命令:

[plain] view
plaincopy





./expect_scp 192.168.75.130 root 123456 /root/src_file /root/dest_file

以上的命令执行后,将把本地/root目录下的src_file文件拷贝到用户名为root,密码为123456的主机192.168.75.130中的/root下,同时还将这个源文件重命名为dest_file。

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

如果需要实现批量scp的任务,则需要再写一个shell脚本来调用这个expect脚本。

[plain] view
plaincopy





#!/bin/sh

list_file=$1

src_file=$2

dest_file=$3

cat $list_file | while read line

do

host_ip=`echo $line | awk '{print $1}'`

username=`echo $line | awk '{print $2}'`

password=`echo $line | awk '{print $3}'`

echo "$host_ip"

./expect_scp $host_ip $username $password $src_file $dest_file

done

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

IP username password

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

192.168.75.130 root 123456

192.168.75.131 knktc testpass

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

执行脚本:

[plain] view
plaincopy





./batch_scp.sh ./hosts.list /root/src_file /root/destfile

用这两个脚本文件,就可以简单地完成批量scp的任务了。


六、综合例子


1、自动化脚本建立主机之间的SSH信任关系

[plain] view
plaincopy





#!/usr/bin/ksh

#usage ./ssh_trust.sh host1 user1 passwd1 host2 user2 passwd2

#即建立从user1@host1到user2@host2的ssh信任。

src_host=$1

src_username=$2

src_passwd=$3

dst_host=$4

dst_username=$5

dst_passwd=$6

#在远程主机1上生成公私钥对

Keygen()

{

expect << EOF

spawn ssh $src_username@$src_host ssh-keygen -t rsa

while 1 {

expect {

"password:" {

send "$src_passwd\n"

}

"yes/no*" {

send "yes\n"

}

"Enter file in which to save the key*" {

send "\n"

}

"Enter passphrase*" {

send "\n"

}

"Enter same passphrase again:" {

send "\n"

}

"Overwrite (y/n)" {

send "n\n"

}

eof {

exit

}

}

}

EOF

}

#从远程主机1获取公钥保存到本地

Get_pub()

{

expect << EOF

spawn scp $src_username@$src_host:~/.ssh/id_rsa.pub /tmp

expect {

"password:" {

send "$src_passwd\n";exp_continue

}

"yes/no*" {

send "yes\n";exp_continue

}

eof {

exit

}

}

EOF

}

#将公钥的内容附加到远程主机2的authorized_keys

Put_pub()

{

src_pub="$(cat /tmp/id_rsa.pub)"

expect << EOF

spawn ssh $dst_username@$dst_host "mkdir -p ~/.ssh;echo $src_pub >> ~/.ssh/authorized_keys;chmod 600 ~/.ssh/authorized_keys"

expect {

"password:" {

send "$dst_passwd\n";exp_continue

}

"yes/no*" {

send "yes\n";exp_continue

}

eof {

exit

}

}

EOF

}

Keygen

Get_pub

Put_pub
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: