關於shell和expect和ssh

以前看到一些大神說,做爲一個運維,一個系統工程師的能力的其中一個很重要的檢驗標準就是他可以管理多少臺機器,他可以自動化到什麼程度,他可以多懶!---因此我也來班門弄斧了,因此就有了這篇文章。html

在現今高度發展的it社會,已經有不少的自動化管理程序了,例如Puppet,Salt,func,Capistrano .......並且還有云虛擬化OpenStack,kvm,xen.....尤爲Docker更是新生代黑馬,爲自動化管理而生的。但存在即爲合理,你有高大上,我也有土肥圓,相對於快捷,簡單的管理小批量linux機器,ssh和expect是很是好用的。linux


Expect是什麼

他是一枚程序,是基於uucp(Unix to Unix Copy Protocol)的 發送/預期 的序列設計而來的。shell

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:api

  • 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。bash

因此expect的工做流程是相似聊天的流程:網絡

A跟B說 hello
B發現A跟他說hello,而後就回復hi
而後A XXXXX
而後B 發現A 在說XXXXX,因此就回復OOOOO
.......

理解的話能夠這樣理解,雖然不夠完整,但不失其意義。併發

而後既然知道了expect是怎麼起做用的,那麼的話就能夠構思咱們的自動化管理設計了,由於expect的設計原理就是爲了去處理「交互式」,把「交互式」處理以後,人爲的干預就少了,天然就實現自動化了。app

通常的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是調用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,之後就不會提示了。less

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沒開,或者端口不對,或者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/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收集爲一個,而後使用相似switch-case的模式,匹配哪一個就觸發哪一個,而且須要執行下一步動做的則須要加上exp_continue,其實這裏就跟普通程序裏面的控制循環的continue是同樣的用法的。

這是執行結果:

[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

再高端一點點能夠這樣,支持變量定義和傳參功能

#!/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就是用來變量定義的,而傳參的話就是使用一個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.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作個循環就能夠簡單實現批量管理

[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

  • 打開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 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

科普時間

1.關於expect的-f 和--

-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.)

--是用來作個限制,限制參數到此爲止。也是可加可不加。

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和--

expect的字符處理是沒有換行符之類的,因此須要額外加上,\r表明是返回字符,表明輸入到此爲止,須要返回,其實效果相似按回車,爲何有些地方用\r,有些地方用\n,其實也無妨,只是爲了輸出格式好看,而\n其實等於了\r\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".

而--是強制下一個參數改成字符串來使用,有點相似強制文本化的效果。

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.)

參考引用:

  1. http://bbs.chinaunix.net/thread-594417-1-1.html
  2. http://www.admin-magazine.com/Articles/Automating-with-Expect-Scripts
  3. http://www.tcl.tk/man/expect5.31/expect.1.html
  4. http://linux.about.com/library/cmd/blcmdln_lindex.htm

原文連接:http://www.godblessyuan.com/2015/02/25/shell_expect_ssh/

相關文章
相關標籤/搜索