expect脚本如何执行获得输出

shell结合expect写的批量scp脚本工具
投稿:mdxy-dxy
字体:[ ] 类型:转载 时间:
expect用于自动化地执行linux环境下的命令行交互任务,例如scp、ssh之类需要用户手动输入密码然后确认的任务。有了这个工具,定义在scp过程中可能遇到的情况,然后编写相应的处理语句,就可以自动地完成scp操作了
在部署一个任务时,其中有一项必须的过程就是将一些文件,如安装包发送到大量的服务器上去。虽然已有宇哥的脚本可用:通过paramiko模块提供的ssh和scp功能编写的python脚本。但我到现在还在对python的恐惧之中(虽然已经在空闲时间努力去学习了),所以使用了shell和expect脚本结合的方式,写了这个批量scp的脚本工具。
expect用于自动化地执行linux环境下的命令行交互任务,例如scp、ssh之类需要用户手动输入密码然后确认的任务。有了这个工具,定义在scp过程中可能遇到的情况,然后编写相应的处理语句,就可以自动地完成scp操作了。
需要expect工具的话可以在linux环境中使用apt-get或pacman这些包管理工具去获取安装,或是到expect开源项目的网站: 来获取。
安装expect之后,可以尝试使用以下的代码来完成对单个服务器的scp任务:
#!/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
"(yes/no)?"
send "yes\n"
expect "*assword:" { send "$password\n"}
"*assword:"
send "$password\n"
expect "100%"
expect eof
注意代码刚开始的第一行,指定了expect的路径,与shell脚本相同,这一句指定了程序在执行时到哪里去寻找相应的启动程序。代码刚开始还设定了timeout的时间为10秒,如果在执行scp任务时遇到了代码中没有指定的异常,则在等待10秒后该脚本的执行会自动终止。
从以上代码刚开始的几行可以看出,我为这个脚本设置了5个需要手动输入的参数,分别为:目标主机的IP、用户名、密码、本地文件路径、目标主机中的文件路径。如果将以上脚本保存为expect_scp文件,则在shell下执行时需要按以下的规范来输入命令:
./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。
spawn代表在本地终端执行的语句,在该语句开始执行后,expect开始捕获终端的输出信息,然后做出对应的操作。expect代码中的捕获的(yes/no)内容用于完成第一次访问目标主机时保存密钥的操作。有了这一句,scp的任务减少了中断的情况。代码结尾的expect eof与spawn对应,表示捕获终端输出信息的终止。
有了这段expect的代码,还只能完成对单个远程主机的scp任务。如果需要实现批量scp的任务,则需要再写一个shell脚本来调用这个expect脚本。
shell脚本:
list_file=$1
src_file=$2
dest_file=$3
cat $list_file | while read line
&& 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
指定了3个参数:列表文件的位置、本地源文件路径、远程主机目标文件路径。需要说明的是其中的列表文件指定了远程主机ip、用户名、密码,这些信息需要写成以下的格式:
IP username password
中间用空格或tab键来分隔,多台主机的信息需要写多行内容,如:
192.168.75.130 root 123456
192.168.75.131 knktc testpass
这样就指定了两台远程主机的信息。注意,如果远程主机密码中有“$”、“#”这类特殊字符的话,在编写列表文件时就需要在这些特殊字符前加上转义字符,否则expect在执行时会输入错误的密码。
对于这个shell脚本,保存为batch_scp.sh文件,与刚才保存的expect_scp文件和列表文件(就定义为hosts.list文件吧)放到同一目录下,执行时按照以下方式输入命令就可以了:
./batch_scp.sh ./hosts.list /root/src_file /root/destfile
用这两个脚本文件,就可以简单地完成批量scp的任务了。
其实批量scp的任务并不难,但是批量ssh的任务可能就会遇到麻烦了。
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具本帖子已过去太久远了,不再提供回复功能。温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!&&|&&
LOFTER精选
网易考拉推荐
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
阅读(2837)|
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
历史上的今天
loftPermalink:'',
id:'fks_087068',
blogTitle:'如何获得expect中spawn进程的返回值',
blogAbstract:'一、返回值校验返回值校验始终是一个安全可靠软件或者说软件或者说作业的基本流程,无论执行什么工作,都应该告诉使用者这个子进程执行的结果如何,就是通常所说的,你倒是吱一声啊。对于expect来说,它的情况是比较特殊的,它执行ssh执行远程进程的时候,我们在expect中读取的只能是ssh的返回值,而不是通过ssh执行的命令的返回值。如果要获得ssh执行的远端进程的返回值,这个是用通用的方法就可以了。例如我们执行了一个scp命令,执行之后可以在命令提示符的正则表达式中做特殊处理,方法就是添加一些变量表示是第几次出现命令提示符命令,之后更具次数来执行shell的内置变量 $? 获得刚刚执行的子进程的返回值,再通过senduser或者其它的脚本变量将这个返回值保存起来,在进程退出的时候将这个值返回给执行者。',
blogTag:'',
blogUrl:'blog/static/',
isPublished:1,
istop:false,
modifyTime:9,
publishTime:7,
permalink:'blog/static/',
commentCount:0,
mainCommentCount:0,
recommendCount:0,
bsrk:-100,
publisherId:0,
recomBlogHome:false,
currentRecomBlog:false,
attachmentsFileIds:[],
groupInfo:{},
friendstatus:'none',
followstatus:'unFollow',
pubSucc:'',
visitorProvince:'',
visitorCity:'',
visitorNewUser:false,
postAddInfo:{},
mset:'000',
remindgoodnightblog:false,
isBlackVisitor:false,
isShowYodaoAd:false,
hostIntro:'',
hmcon:'1',
selfRecomBlogCount:'0',
lofter_single:''
{list a as x}
{if x.moveFrom=='wap'}
{elseif x.moveFrom=='iphone'}
{elseif x.moveFrom=='android'}
{elseif x.moveFrom=='mobile'}
${a.selfIntro|escape}{if great260}${suplement}{/if}
{list a as x}
推荐过这篇日志的人:
{list a as x}
{if !!b&&b.length>0}
他们还推荐了:
{list b as y}
转载记录:
{list d as x}
{list a as x}
{list a as x}
{list a as x}
{list a as x}
{if x_index>4}{break}{/if}
${fn2(x.publishTime,'yyyy-MM-dd HH:mm:ss')}
{list a as x}
{if !!(blogDetail.preBlogPermalink)}
{if !!(blogDetail.nextBlogPermalink)}
{list a as x}
{if defined('newslist')&&newslist.length>0}
{list newslist as x}
{if x_index>7}{break}{/if}
{list a as x}
{var first_option =}
{list x.voteDetailList as voteToOption}
{if voteToOption==1}
{if first_option==false},{/if}&&“${b[voteToOption_index]}”&&
{if (x.role!="-1") },“我是${c[x.role]}”&&{/if}
&&&&&&&&${fn1(x.voteTime)}
{if x.userName==''}{/if}
网易公司版权所有&&
{list x.l as y}
{if defined('wl')}
{list wl as x}{/list}yujia 的BLOG
用户名:yujia
文章数:225
评论数:14
访问量:35519
注册日期:
阅读量:5863
阅读量:12276
阅读量:359912
阅读量:1055849
51CTO推荐博文
之前看到一些大神说,作为一个运维,一个系统工程师的能力的其中一个很重要的检验标准就是他能够管理多少台机器,他能够自动化到什么程度,他能够多懒!―所以我也来班门弄斧了,所以就有了这篇文章。在现今高度发展的it社会,已经有很多的自动化管理程序了,例如Puppet,Salt,func,Capistrano …….而且还有云虚拟化OpenStack,kvm,xen…..尤其Docker更是新生代黑马,为自动化管理而生的。但存在即为合理,你有高大上,我也有土肥圆,相对于快捷,简单的管理小批量linux机器,ssh和expect是非常好用的Expect是什么他是一枚程序,是基于uucp(Unix to Unix Copy Protocol)的 发送/预期 的序列设计而来的。The name “Expect” comes from the idea of send/expect sequences popularized by uucp, kermit and other modem control programs. However unlike uucp, Expect is generalized so that it can be run as a user-level command with any program and task in mind. Expect can actually talk to several programs at the same time.For example, here are some things Expect can do:Cause your computer to dial you back, so that you can login without paying for the call.Start a game (e.g., rogue) and if the optimal configuration doesn’t appear, restart it (again and again) until it does, then hand over control to you.Run fsck, and in response to its questions, answer “yes”, “no” or give control back to you, based on predetermined criteria.Connect to another network or BBS (e.g., MCI Mail, CompuServe) and automatically retrieve your mail so that it appears as if it was originally sent to your local system.Carry environment variables, current directory, or any kind of information across rlogin, telnet, tip, su, chgrp, etc.从最简单的层次来说,Expect的工作方式象一个通用化的Chat脚本工具。Chat脚本最早用于UUCP网络内,以用来实现计算机之间需要建立连接时进行特定的登录会话的自动化。Chat脚本由一系列expect-send对组成:expect等待输出中输出特定的字符,通常是一个提示符,然后发送特定的响应。例如下面的Chat脚本实现等待标准输出出现Login:字符串,然后发送somebody作为用户名;然后等待Password:提示符,并发出响应sillyme。所以expect的工作流程是类似聊天的流程:A跟B说&hello
B发现A跟他说hello,然后就回复hi然后A&XXXXX然后B&发现A&在说XXXXX,所以就回复OOOOO.......理解的话可以这样理解,虽然不够完整,但不失其意义。然后既然知道了expect是怎么起作用的,那么的话就可以构思我们的自动化管理设计了,因为expect的设计原理就是为了去处理“交互式”,把“交互式”处理之后,人为的干预就少了,自然就实现自动化了。一般的expect 使用#!/usr/bin/expectset&timeout&5spawn&ssh&192.168.6.136&-p&1024expect&"password"&{send&"123passwd\n"}expect&&"Last&login"&{send&"&ifconfig&|grep&eth0&-A3\n"}expect&eofexit#!/usr/bin/expect是调用expect的写法,这个跟一般的shell 写 #!/bin/bash是不同的,这里的意义是以下内容是以什么方式运行,写expect就是expect的方式,写bash就是bash。spawn是创建一个进程,就是使用expect的时候是要运行expect进程的,spwan就是代表需要创建这样的进程的意思,理解为create也可以,这里就是创建一个ssh 连接的进程,后面的写法跟一般ssh连接执行命令无异。timeout 表示这个expect动作的生存时间,根据我的理解,例如设置为5秒,那么执行一次expect后就要等待5秒。expect eof和exit是指监测到eof就会执行exit,退出程序。高端一点点可以改为……我们观察一般的ssh正常交互会有哪些情况,首次连接提示,成功连接后会生成knowhost,以后就不会提示了。ssh&192.168.6.136&-p&1024The&authenticity&of&host&'[192.168.6.136]:1024&([192.168.6.136]:1024)'&can't&be&established.
RSA&key&fingerprint&is&7d:68:97:bc:f8:c1:b7:8a:a9:98:5a:03:4a:77:b9:eb.
Are&you&sure&you&want&to&continue&connecting&(yes/no)?&yes
Warning:&Permanently&added&'[192.168.6.136]:1024'&(RSA)&to&the&list&of&known&hosts.
root@192.168.6.136's&password:正常连接提示:ssh&192.168.6.136&-p&1024root@192.168.6.136's&password:连接被拒绝,可能是ssh没开,或者端口不对,或者iptables限制:ssh&192.168.6.136&ssh:&connect&to&host&192.168.6.136&port&22:&Connection&refused没有连接地址:ssh&sadas&
ssh:&Could&not&resolve&hostname&sadas:&Name&or&service&not&known所以可以改成这样:#!/usr/bin/expectset&timeout&5spawn&ssh&192.168.6.136&-p&1024expect&{
&&&&"Connection&refused"&exit
&&&&"Name&or&service&not&known"&exit
&&&&"continue&connecting"&{send&"yes\r";exp_continue}
&&&&"password:"&{send&"123passwd\r";exp_continue}
&&&&"Last&login"&{send&"&ifconfig&|grep&eth0&-A3\n"}}
&&&&expect&eof
&&&&exit将所以的expect收集为一个,然后使用类似switch-case的模式,匹配哪个就触发哪个,并且需要执行下一步动作的则需要加上exp_continue,其实这里就跟普通程序里面的控制循环的continue是一样的用法的。这是执行结果:[root@localhost&test_shell_expect]#&./test3.sh&&spawn&ssh&192.168.6.136&-p&1024root@192.168.6.136's&password:&
Last&login:&Wed&Feb&25&07:07:42&2015&from&192.168.6.127
ifconfig&|grep&eth0&-A3
[root@wohost&~]#&&ifconfig&|grep&eth0&-A3
eth0&&&&Link&encap:Ethernet&&HWaddr&00:0C:29:DE:E9:90&&
&&&&&&&&inet&addr:192.168.6.136&&Bcast:192.168.6.255&&Mask:255.255.255.0
&&&&&&&&inet6&addr:&fe80::20c:29ff:fede:e990/64&Scope:Link
&&&&&&&&UP&BROADCAST&RUNNING&MULTICAST&&MTU:1500&&Metric:1再高端一点点可以这样,支持变量定义和传参功能#!/usr/bin/expect&set&timeout&5set&pw&"123passwd"set&host&[lindex&$argv&0]spawn&ssh&$host&-p&1024expect&{
&&&&"Connection&refused"&exit
&&&&"Name&or&service&not&known"&exit
&&&&"continue&connecting"&{send&"yes\r";exp_continue}
&&&&"password:"&{send&"$pw\r";exp_continue}
&&&&"Last&login"&{send&"&ifconfig&|grep&eth0&-A3\n"}}&expect&eofexitset就是用来变量定义的,而传参的话就是使用一个lindex $argv 0 的方式,$argv是指参数项数组,lindex将参数项数组的列表生成出来,然后 0 代表的是使用第一个值,不过这里有个小疑问我还没有完全理解,就是参考debug模式可以看到argv[0] = /usr/bin/expect argv[1] = -d argv[2] = ./test3.sh argv[3] = 192.168.6.136 ,第一个值应该argv[0] = /usr/bin/expect才对,但是程序能够获取到192.168.6.136,我暂时的理解就是他读取的是我执行命令的第一个参数,例如./test3.sh 192.168.6.136,所以第一个参数就是192.168.6.136,如此类推。效果:./test3.sh 192.168.6.136spawn&ssh&192.168.6.136&-p&1024root@192.168.6.136's&password:&
Last&login:&Wed&Feb&25&07:11:17&2015&from&192.168.6.127
&ifconfig&|grep&eth0&-A3
[root@wohost&~]#&&ifconfig&|grep&eth0&-A3
eth0&&&&Link&encap:Ethernet&&HWaddr&00:0C:29:DE:E9:90&&
&&&&&&&&inet&addr:192.168.6.136&&Bcast:192.168.6.255&&Mask:255.255.255.0
&&&&&&&&inet6&addr:&fe80::20c:29ff:fede:e990/64&Scope:Link
&&&&&&&&UP&BROADCAST&RUNNING&MULTICAST&&MTU:1500&&Metric:1然后配合shell做个循环就可以简单实现批量管理[root@localhost&test_shell_expect]#&cat&test2.sh&#!/bin/bashwhile&read&hostdo&&&&&&&&&&&
&&&&./test1.exp&$host&
done&&file.txt&&
[root@localhost&test_shell_expect]#&cat&file.txt&&&log&&&&&&&&test1.exp&&test2.sh&&&test3.sh&&&
[root@localhost&test_shell_expect]#&cat&file.txt&192.168.6..6.127大功告成。troubleshooting打开debug模式,使用-d,可以方便调试并且观看expect的执行过程。#!/usr/bin/expect -d输出效果如下:./test3.sh&192.168.6.136
expect&version&5.44.1.15
argv[0]&=&/usr/bin/expect&&argv[1]&=&-d&&argv[2]&=&./test3.sh&&argv[3]&=&192.168.6.136&&set&argc&1set&argv0&"./test3.sh"set&argv&"192.168.6.136"executing&commands&from&command&file&./test3.sh
spawn&ssh&192.168.6.136&-p&1024parent:&waiting&for&sync&byteparent:&telling&child&to&go&ahead
parent:&now&unsynchronized&from&child
spawn:&returns&{7991}expect:&does&""&(spawn_id&exp4)&match&glob&pattern&"Connection&refused"?&no"Name&or&service&not&known"?&no"continue&connecting"?&no"password:"?&no"Last&login"?&noroot@192.168.6.136's&password:&
expect:&does&"root@192.168.6.136's&password:&"&(spawn_id&exp4)&match&glob&pattern&"Connection&refused"?&no
"Name&or&service&not&known"?&no
"continue&connecting"?&no
"password:"?&yes
expect:&set&expect_out(0,string)&"password:"
expect:&set&expect_out(spawn_id)&"exp4"
expect:&set&expect_out(buffer)&"root@192.168.6.136's&password:"
send:&sending&"123passwd\r"&to&{&exp4&}
expect:&continuing&expect
expect:&does&"&"&(spawn_id&exp4)&match&glob&pattern&"Connection&refused"?&no
"Name&or&service&not&known"?&no
"continue&connecting"?&no
"password:"?&no
"Last&login"?&no
expect:&does&"&\r\n"&(spawn_id&exp4)&match&glob&pattern&"Connection&refused"?&no
"Name&or&service&not&known"?&no
"continue&connecting"?&no
"password:"?&no
"Last&login"?&no
Last&login:&Wed&Feb&25&07:14:06&2015&from&192.168.6.127
expect:&does&"&\r\nLast&login:&Wed&Feb&25&07:14:06&2015&from&192.168.6.127\r\r\n"&(spawn_id&exp4)&match&glob&pattern&"Connection&refused"?&no
"Name&or&service&not&known"?&no
"continue&connecting"?&no
"password:"?&no
"Last&login"?&yes
expect:&set&expect_out(0,string)&"Last&login"
expect:&set&expect_out(spawn_id)&"exp4"
expect:&set&expect_out(buffer)&"&\r\nLast&login"
send:&sending&"&ifconfig&|grep&eth0&-A3\n"&to&{&exp4&}
&ifconfig&|grep&eth0&-A3
[root@wohost&~]#&&ifconfig&|grep&eth0&-A3
eth0&&Link&encap:Ethernet&&HWaddr&00:0C:29:DE:E9:90&&
&&&&&&&&inet&addr:192.168.6.136&&Bcast:192.168.6.255&&Mask:255.255.255.0
&&&&&&&&inet6&addr:&fe80::20c:29ff:fede:e990/64&Scope:Link
&&&&&&&&UP&BROADCAST&RUNNING&MULTICAST&&MTU:1500&&Metric:1科普时间1.关于expect的-f 和C-f 其实可加可不加,因为他只是说是从一个文件读取命令,他是一个可选项,仅在使用#!的时候要,根据我测试,其实不加也可以。The&-f&flag&prefaces&a&file&from&which&to&read&commands&from.&The&flag&itself&is&optional&as&it&is&only&useful&when&using&the&#!&notation&(see&above),&so&that&other&arguments&may&be&supplied&on&the&command&line.&(When&using&Expectk,&this&option&is&specified&as&-file.)By&default,&the&command&file&is&read&into&memory&and&executed&in&its&entirety.&It&is&occasionally&desirable&to&read&files&one&line&at&a&time.&For&example,&stdin&is&read&this&way.&In&order&to&force&arbitrary&files&to&be&handled&this&way,&use&the&-b&flag.&(When&using&Expectk,&this&option&is&specified&as&-buffer.)C是用来做个限制,限制参数到此为止。也是可加可不加。may&be&used&to&delimit&the&end&of&the&options.&This&is&useful&if&you&want&to&pass&an&option-like&argument&to&your&script&without&it&being&interpreted&by&Expect.&This&can&usefully&be&placed&in&the&#!&line&to&prevent&any&flag-like&interpretation&by&Expect.&For&example,&the&following&will&leave&the&original&arguments&(including&the&script&name)&in&the&variable&argv.#!/usr/local/bin/expect&--Note&that&the&usual&getopt(3)&and&execve(2)&conventions&must&be&observed&when&adding&arguments&to&the&#!&line.2.关于send的\r和Cexpect的字符处理是没有换行符之类的,所以需要额外加上,\r代表是返回字符,代表输入到此为止,需要返回,其实效果类似按回车,为什么有些地方用\r,有些地方用\n,其实也无妨,只是为了输出格式好看,而\n表示换行,所以会多一个空行。Sends&string&to&the&current&process.&For&example,&the&command
send&"hello&world\r"sends&the&characters,&h&e&l&l&o&&blank&&w&o&r&l&d&&return&&to&the&current&process.&(Tcl&includes&a&printf-like&command&(called&format)&which&can&build&arbitrarily&complex&strings.)Characters&are&sent&immediately&although&programs&with&line-buffered&input&will&not&read&the&characters&until&a&return&character&is&sent.&A&return&character&is&denoted&"\r".而C是强制下一个参数改为字符串来使用,有点类似强制文本化的效果。The&--&flag&forces&the&next&argument&to&be&interpreted&as&a&string&rather&than&a&flag.&Any&string&can&be&preceded&by&"--"&whether&or&not&it&actually&looks&like&a&flag.&This&provides&a&reliable&mechanism&to&specify&variable&strings&without&being&tripped&up&by&those&that&accidentally&look&like&flags.&(All&strings&starting&with&"-"&are&reserved&for&future&options.)参考引用:&&
了这篇文章
类别:┆阅读(0)┆评论(0)

我要回帖

更多关于 expect 脚本 的文章

 

随机推荐