之前看到一些大神說(shuō),作為一個(gè)運(yùn)維歌焦,一個(gè)系統(tǒng)工程師的能力的其中一個(gè)很重要的檢驗(yàn)標(biāo)準(zhǔn)就是他能夠管理多少臺(tái)機(jī)器淮捆,他能夠自動(dòng)化到什么程度,他能夠多懶!---所以我也來(lái)班門(mén)弄斧了躯护,所以就有了這篇文章惊来。
在現(xiàn)今高度發(fā)展的it社會(huì),已經(jīng)有很多的自動(dòng)化管理程序了棺滞,例如Puppet裁蚁,Salt矢渊,func,Capistrano .......而且還有云虛擬化OpenStack,kvm,xen.....尤其Docker更是新生代黑馬枉证,為自動(dòng)化管理而生的矮男。但存在即為合理,你有高大上室谚,我也有土肥圓毡鉴,相對(duì)于快捷,簡(jiǎn)單的管理小批量linux機(jī)器秒赤,ssh和expect是非常好用的猪瞬。
Expect是什么
他是一枚程序,是基于uucp(Unix to Unix Copy Protocol)的 發(fā)送/預(yù)期 的序列設(shè)計(jì)而來(lái)的倒脓。
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.
從最簡(jiǎn)單的層次來(lái)說(shuō)撑螺,Expect的工作方式象一個(gè)通用化的Chat腳本工具。Chat腳本最早用于UUCP網(wǎng)絡(luò)內(nèi)崎弃,以用來(lái)實(shí)現(xiàn)計(jì)算機(jī)之間需要建立連接時(shí)進(jìn)行特定的登錄會(huì)話(huà)的自動(dòng)化甘晤。
Chat腳本由一系列expect-send對(duì)組成:expect等待輸出中輸出特定的字符,通常是一個(gè)提示符饲做,然后發(fā)送特定的響應(yīng)线婚。例如下面的Chat腳本實(shí)現(xiàn)等待標(biāo)準(zhǔn)輸出出現(xiàn)Login:字符串,然后發(fā)送somebody作為用戶(hù)名盆均;然后等待Password:提示符塞弊,并發(fā)出響應(yīng)sillyme。
所以expect的工作流程是類(lèi)似聊天的流程:
A跟B說(shuō) hello
B發(fā)現(xiàn)A跟他說(shuō)hello泪姨,然后就回復(fù)hi
然后A XXXXX
然后B 發(fā)現(xiàn)A 在說(shuō)XXXXX游沿,所以就回復(fù)OOOOO
.......
理解的話(huà)可以這樣理解,雖然不夠完整肮砾,但不失其意義诀黍。
然后既然知道了expect是怎么起作用的,那么的話(huà)就可以構(gòu)思我們的自動(dòng)化管理設(shè)計(jì)了仗处,因?yàn)閑xpect的設(shè)計(jì)原理就是為了去處理“交互式”眯勾,把“交互式”處理之后,人為的干預(yù)就少了,自然就實(shí)現(xiàn)自動(dòng)化了婆誓。
一般的expect 使用
#!/usr/bin/expect
set timeout 5
spawn ssh 192.168.6.136 -p 1024
expect "password" {send "123passwd\n"}
expect "Last login" {send " ifconfig |grep eth0 -A3\n"}
expect eof
exit
-
!/usr/bin/expect是調(diào)用expect的寫(xiě)法吃环,這個(gè)跟一般的shell 寫(xiě) #!/bin/bash是不同的,這里的意義是以下內(nèi)容是以什么方式運(yùn)行洋幻,寫(xiě)expect就是expect的方式郁轻,寫(xiě)bash就是bash。
- spawn是創(chuàng)建一個(gè)進(jìn)程鞋屈,就是使用expect的時(shí)候是要運(yùn)行expect進(jìn)程的范咨,spwan就是代表需要?jiǎng)?chuàng)建這樣的進(jìn)程的意思故觅,理解為create也可以,這里就是創(chuàng)建一個(gè)ssh 連接的進(jìn)程渠啊,后面的寫(xiě)法跟一般ssh連接執(zhí)行命令無(wú)異输吏。
- timeout 表示這個(gè)expect動(dòng)作的生存時(shí)間,根據(jù)我的理解替蛉,例如設(shè)置為5秒,那么執(zhí)行一次expect后就要等待5秒它浅。
- expect eof和exit是指監(jiān)測(cè)到eof就會(huì)執(zhí)行exit镣煮,退出程序姐霍。
高端一點(diǎn)點(diǎn)可以改為......
我們觀察一般的ssh正常交互會(huì)有哪些情況,首次連接提示典唇,成功連接后會(huì)生成knowhost,以后就不會(huì)提示了恨胚。
ssh 192.168.6.136 -p 1024
The 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 1024
root@192.168.6.136's password:
連接被拒絕洋侨,可能是ssh沒(méi)開(kāi)秧了,或者端口不對(duì),或者iptables限制:
ssh 192.168.6.136
ssh: connect to host 192.168.6.136 port 22: Connection refused
沒(méi)有連接地址:
ssh sadas
ssh: Could not resolve hostname sadas: Name or service not known
所以可以改成這樣:
#!/usr/bin/expect
set timeout 5
spawn ssh 192.168.6.136 -p 1024
expect {
"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收集為一個(gè)鞋真,然后使用類(lèi)似switch-case的模式,匹配哪個(gè)就觸發(fā)哪個(gè)绸栅,并且需要執(zhí)行下一步動(dòng)作的則需要加上exp_continue,其實(shí)這里就跟普通程序里面的控制循環(huán)的continue是一樣的用法的。
這是執(zhí)行結(jié)果:
[root@localhost test_shell_expect]# ./test3.sh
spawn ssh 192.168.6.136 -p 1024
root@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
再高端一點(diǎn)點(diǎn)可以這樣矛双,支持變量定義和傳參功能
#!/usr/bin/expect
set timeout 5
set pw "123passwd"
set host [lindex $argv 0]
spawn ssh $host -p 1024
expect {
"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 eof
exit
- set就是用來(lái)變量定義的蟆豫,而傳參的話(huà)就是使用一個(gè)lindex $argv 0 的方式,$argv是指參數(shù)項(xiàng)數(shù)組十减,lindex將參數(shù)項(xiàng)數(shù)組的列表生成出來(lái),然后 0 代表的是使用第一個(gè)值速址,不過(guò)這里有個(gè)小疑問(wèn)我還沒(méi)有完全理解,就是參考debug模式可以看到argv[0] = /usr/bin/expect argv[1] = -d argv[2] = ./test3.sh argv[3] = 192.168.6.136 昔园,第一個(gè)值應(yīng)該argv[0] = /usr/bin/expect才對(duì)并炮,但是程序能夠獲取到192.168.6.136,我暫時(shí)的理解就是他讀取的是我執(zhí)行命令的第一個(gè)參數(shù)荤西,例如./test3.sh 192.168.6.136伍俘,所以第一個(gè)參數(shù)就是192.168.6.136,如此類(lèi)推秃流。
效果:
./test3.sh 192.168.6.136
spawn ssh 192.168.6.136 -p 1024
root@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做個(gè)循環(huán)就可以簡(jiǎn)單實(shí)現(xiàn)批量管理
[root@localhost test_shell_expect]# cat test2.sh
#!/bin/bash
while read host
do
./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.136
192.168.6.127
大功告成柳弄。
troubleshooting
- 打開(kāi)debug模式,使用-d嚣伐,可以方便調(diào)試并且觀看expect的執(zhí)行過(guò)程萍丐。
!/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 1
set argv0 "./test3.sh"
set argv "192.168.6.136"
executing commands from command file ./test3.sh
spawn ssh 192.168.6.136 -p 1024
parent: waiting for sync byte
parent: 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"? no
root@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
科普時(shí)間
1.關(guān)于expect的-f 和--
-f 其實(shí)可加可不加基茵,因?yàn)樗皇钦f(shuō)是從一個(gè)文件讀取命令,他是一個(gè)可選項(xiàng)壳影,僅在使用#!的時(shí)候要根灯,根據(jù)我測(cè)試,其實(shí)不加也可以烙肺。
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.)
--是用來(lái)做個(gè)限制桃笙,限制參數(shù)到此為止。也是可加可不加丽猬。
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.關(guān)于send的\r和--
expect的字符處理是沒(méi)有換行符之類(lèi)的熏瞄,所以需要額外加上,\r代表是返回字符由桌,代表輸入到此為止邮丰,需要返回,其實(shí)效果類(lèi)似按回車(chē)娃循,為什么有些地方用\r斗蒋,有些地方用\n,其實(shí)也無(wú)妨泉沾,只是為了輸出格式好看,而\n其實(shí)等于了\r\n了姓迅,所以會(huì)多一個(gè)空行俊马。
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".
而--是強(qiáng)制下一個(gè)參數(shù)改為字符串來(lái)使用,有點(diǎn)類(lèi)似強(qiáng)制文本化的效果柱嫌。
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.)
參考引用:
- http://bbs.chinaunix.net/thread-594417-1-1.html
- http://www.admin-magazine.com/Articles/Automating-with-Expect-Scripts
- http://www.tcl.tk/man/expect5.31/expect.1.html
- http://linux.about.com/library/cmd/blcmdln_lindex.htm
原文鏈接:http://www.godblessyuan.com/2015/02/25/shell_expect_ssh/