一、概述
我们通过 Shell 可以实现简单的控制流功能,如:循环、判断等。但是对于需要交互的场合则必须通过人工来干预,有时候我们可能会需要实现和交互程序如 telnet 服务器等进行交互的功能。而 expect 就使用来实现这种功能的工具。
expect是一个免费的编程工具语言,用来实现自动和交互式任务进行通信,而无需人的干预。expect是不断发展的,随着时间的流逝,其功能越来越强大,已经成为系统管理员的的一个强大助手。expect需要Tcl编程语言的支持,要在系统上运行expect必须首先安装Tcl。
二、expect 的安装
expect 是在 Tcl 基础上创建起来的,所以在安装 expect 前我们应该先安装 Tcl。
(一)Tcl 安装
下载地址: https://nchc.dl.sourceforge.net/project/tcl/Tcl/8.4.20/tcl8.4.20-src.tar.gz
1.下载源码包
[plain] view plaincopyprint?
2.解压缩源码包
[plain] view plaincopyprint?
- tar xfvz tcl8.4.11-src.tar.gz
3.安装配置
[plain] view plaincopyprint?
- cd tcl8.4.11/unix
- ./configure --prefix=/usr/tcl --enable-shared
- make
- make install
注意:
1、安装完毕以后,进入 tcl 源代码的根目录,把子目录 unix 下面的 tclUnixPort.h copy 到子目录 generic 中。
2、暂时不要删除 tcl 源代码,因为 expect 的安装过程还需要用。
(二)expect 安装 (需 Tcl 的库)
1.下载源码包
[plain] view plaincopyprint?
2.解压缩源码包
[plain] view plaincopyprint?
- tar xzvf expect5.45.tar.gz
3.安装配置
[plain] view plaincopyprint?
- cd expect5.45
- ./configure --prefix=/usr/expect --with-tcl=/usr/tcl/lib --with-tclinclude=../tcl8.4.11/generic
- make
- make install
- ln -s /usr/tcl/bin/expect /usr/expect/bin/expect
三、Expect 工作原理
从最简单的层次来说,Expect 的工作方式象一个通用化的 Chat 脚本工具。Chat 脚本最早用于 UUCP 网络内,以用来实现计算机之间需要建立连接时进行特定的登录会话的自动化。
Chat脚本由一系列expect-send对组成:expect等待输出中输出特定的字符,通常是一个提示符,然后发送特定的响应。例如下面的 Chat脚本实现等待标准输出出现Login:字符串,然后发送somebody作为用户名;然后等待Password:提示符,并发出响应 sillyme。
引用:
[plain] view plaincopyprint?
- Login: somebody Password: sillyme
Expect 最简单的脚本操作模式本质上和 Chat 脚本工作模式是一样的。
例子:
1、实现功能
下面我们分析一个响应 chsh 命令的脚本。我们首先回顾一下这个交互命令的格式。
假设我们要为用户 chavez 改变登录脚本,要求实现的命令交互过程如下:
[plain] view plaincopyprint?
-
chsh chavez
- Changing the login shell for chavez
- Enter the new value, or press return for the default
- Login Shell [/bin/bash]: /bin/tcsh
可以看到该命令首先输出若干行提示信息并且提示输入用户新的登录 shell。我们必须在提示信息后面输入用户的登录 shell 或者直接回车不修改登录 shell。
2、实现自动执行
[plain] view plaincopyprint?
- #!/usr/bin/expect
-
Change a login shell to tcsh
[plain] view plaincopyprint?
- set user [lindex $argv 0]
- spawn chsh $user
- expect "]:"
- send "/bin/tcsh "
- expect eof
- exit
说明:
(1)首行指定用来执行该脚本的命令程序,这里是/usr/bin/expect。
(2)程序第一行用来获得脚本的执行参数(其保存在数组 $argv 中,从 0 号开始是参数),并将其保存到变量 user 中。
(3)第二个参数使用 expect 的 spawn 命令来启动脚本和命令的会话,这里启动的是 chsh 命令,实际上命令是以衍生子进程的方式来运行的。
(4)随后的 expect 和 send 命令用来实现交互过程。脚本首先等待输出中出现]:字符串,一旦在输出中出现 chsh 输出到的特征字符串(一般特征 字符串往往是等待输入的最后的提示符的特征信息)。对于其他不匹配的信息则会完全忽略。当脚本得到特征字符串时,expect 将发送/bin/tcsh 和 一个回车符给 chsh 命令。最后脚本等待命令退出(chsh 结束),一旦接收到标识子进程已经结束的 eof 字符,expect 脚本也就退出结束。
3、决定如何响应
系统管理员往往有这样的需求,希望根据当前的具体情况来以不同的方式对一个命令进行响应。我们可以通过后面的例子看到 expect 可以实现非常复杂的条件响应,而仅仅通过简单的修改预处理脚本就可以实现。
下面的例子是一个更复杂的 expect-send 例子:
[plain] view plaincopyprint?
- expect -re "[(.*)]:"
- if {$expect_out(1,string)!="/bin/tcsh"} {
- send "/bin/tcsh" }
- send " "
- expect eof
说明:
(1)第一个 expect 命令现在使用了-re 参数,这个参数表示指定的的字符串是一个正则表达式,而不是一个普通的字符串。对于上面这个例子里是查找一个左方括号字符(其必须进行三次逃逸(escape),因此有三个符号,因为它对于 expect 和正则表达时来说都是特殊字符)后面跟有零个或多个字符,最后是一个右方括号字符。这里.*表示表示一个或多个任意字符,将其存放在()中是因为将匹配结果存放在一个变量中以实现随后的对匹配结果的访问。
(2)当发现一个匹配则检查包含在[]中的字符串,查看是否为/bin/tcsh。如果不是则发送/bin/tcsh 给 chsh 命令作为输入,如果是则仅仅发送一个回车符。这个简单的针对具体情况发出不同相响应的小例子说明了 expect 的强大功能。
(3)在一个正则表达时中,可以在()中包含若干个部分并通过 expect_out 数组访问它们。各个部分在表达式中从左到右进行编码,从 1 开始(0 包含有整个匹配输出)。()可能会出现嵌套情况,这这种情况下编码从最内层到最外层来进行的。
4、使用超时
下一个 expect 例子中将阐述具有超时功能的提示符函数。这个脚本提示用户输入,如果在给定的时间内没有输入,则会超时并返回一个默认的响应。这个脚本接收三个参数:提示符字串,默认响应和超时时间(秒)。
[plain] view plaincopyprint?
- #!/usr/bin/expect
-
Prompt function with timeout and default.
- #脚本的第一部分首先是得到运行参数并将其保存到内部变量中
- set prompt [lindex $argv 0]
- set def [lindex $argv 1]
- set response $def
- set tout [lindex $argv 2]
- send_tty "$prompt: "
- #send_tty 命令用来实现在终端上显示提示符字串和一个冒号及空格
- set timeout $tout
- #set timeout 命令设置后面所有的 expect 命令的等待响应的超时时间为 $tout(-l 参数用来关闭任何超时设置)。
- expect " " {
- set raw $expect_out(buffer)
-
remove final carriage return
- set response [string trimright "$raw" " "]
- }
- if {"response" == "} {set response def}
- send "$response "
-
Prompt function with timeout and default.
- set prompt [lindex $argv 0]
- set def [lindex $argv 1]
- set response $def
- set tout [lindex $argv 2]
说明:
(1)send_tty 命令用来实现在终端上显示提示符字串和一个冒号及空格。
(2)set timeout 命令设置后面所有的 expect 命令的等待响应的超时时间为 $tout(-l 参数用来关闭任何超时设置)。
(3)然后 expect 命令就等待输出中出现回车字符。如果在超时之前得到回车符,那么 set 命令就会将用户输入的内容赋值给变脸 raw。随后的命令将用户输入内容最后的回车符号去除以后赋值给变量 response。
(4)如果 response 中内容为空则将 response 值置为默认值(如果用户在超时以后没有输入或者用户仅仅输入了回车符)。最后 send 命令将 response 变量的值加上回车符发送给标准输出。
注意:
(1)该脚本没有使用 spawn 命令。
(2)该 expect 脚本会与任何调用该脚本的进程交互。
(3)如果该脚本名为 prompt,那么它可以用在任何 C 风格的 shell 中。
[plain] view plaincopyprint?
- % set a='prompt "Enter an answer" silence 10'
- Enter an answer: test
- % echo Answer was "$a"
- Answer was test
prompt 设定的超时为 10 秒。如果超时或者用户仅仅输入了回车符号,echo 命令将输出
[plain] view plaincopyprint?
- Answer was "silence"
5、一个更复杂的例子
下面我们将讨论一个更加复杂的 expect 脚本例子,这个脚本使用了一些更复杂的控制结构和很多复杂的交互过程。这个例子用来实现发送 write 命令给任意的用户,发送的消息来自于一个文件或者来自于键盘输入。
[plain] view plaincopyprint?
- #!/usr/bin/expect
-
Write to multiple users from a prepared file
-
or a message input interactively
- if {$argc<2} {
- send_user "usage: $argv0 file user1 user2 ... "
- exit
- }
- #send_user 命令用来显示使用帮助信息到父进程(一般为用户的 shell)的标准输出。
- set nofile 0
-
get filename via the Tcl lindex function
- set file [lindex $argv 0]
- if {$file=="i"} {
- set nofile 1
- } else {
-
make sure message file exists
- if {[file isfile $file]!=1} {
- send_user "argv0: file file not found. "
- exit }}
- ####################################################
- #(1)这部分实现处理脚本启动参数,其必须是一个储存要发送的消息的文件名或表示使用交互输入得到发送消的内容的"i"命令。
- #(2)变量 file 被设置为脚本的第一个参数的值,是通过一个 Tcl 函数 lindex 来实现的,该函数从列表/数组得到一个特定的元素。[]用来实现将函数 lindex 的返回值作为 set 命令的参数。
- #(3)如果脚本的第一个参数是小写的"i",那么变量 nofile 被设置为 1,否则通过调用 Tcl 的函数 isfile 来验证参数指定的文件存在,如果不存在就报错退出。
- #(4)可以看到这里使用了 if 命令来实现逻辑判断功能。该命令后面直接跟判断条件,并且执行在判断条件后的{}内的命令。if 条件为 false 时则运行 else 后的程序块。
- #######################################################
- set procs {}
-
start write processes
- for {set i 1} {i<argc}
- {incr i} {
- spawn -noecho write
- [lindex argv i]
- lappend procs $spawn_id
- }
- #######################################################################################
- #(1)这一部分使用 spawn 命令来启动 write 进程实现向用户发送消息.
- #(2)这里使用了 for 命令来实现循环控制功能,循环变量首先设置为 1,然后因此递增。循环体是最后的{}的内容。
- #(3)这里我们是用脚本的第二个和随后的参数来 spawn 一个 write 命令,并将每个参数作为发送消息的用户名。
- #(4)lappend 命令使用保存每个 spawn 的进程的进程 ID 号的内部变量 $spawn_id 在变量 procs 中构造了一个进程 ID 号列表。
- ###################################################################################################
- if {$nofile==0} {
- setmesg [open "$file" "r"]
- } else {
- send_user "enter message,
- ending with ^D: " }
- #最后脚本根据变量 nofile 的值实现打开消息文件或者提示用户输入要发送的消息。
- set timeout -1
- while 1 {
- if {$nofile==0} {
- if {[gets $mesg chars] == -1} break
- set line "$chars "
- } else {
- expect_user {
- -re " " {}
- eof break }
- set line $expect_out(buffer) }
- foreach spawn_id $procs {
- send $line }
- sleep 1}
- exit
- ########################################################
- #(1)这段代码说明了实际的消息文本是如何通过无限循环 while 被发送的。
- #(2)while 循环中的 if 判断消息是如何得到的。在非交互模式下,下一行内容从消息文件中读出,当文件内容结束时 while 循环也就结束了。(break 命令实现终止循环) 。
- #(3)在交互模式下,expect_user 命令从用户接收消息,当用户输入 ctrl+D 时结束输入,循环同时结束。 两种情况下变量 $line 都被用来保存下一行消息内容。当是消息文件时,回车会被附加到消息的尾部。
- #(4)foreach 循环遍历 spawn 的所有进程,这些进程的 ID 号都保存在列表变量 $procs 中,实现分别和各个进程通信。send 命令组成了 foreach 的循环体,发送一行消息到当前的 write 进程。while 循环的最后是一个 sleep 命令,主要是用于处理非交互模式情况下,以确保消息 不会太快的发送给各个 write 进程。当 while 循环退出时,expect 脚本结束。
- ########################################################
四、使用 expect 脚本的小窍门
1、使用“-c”选项,从命令行执行 expect 脚本
expect 可以让你使用“-c”选项,直接在命令行中执行它,如下所示:
[plain] view plaincopyprint?
- $ expect -c 'expect "\n" {send "pressed enter\n"}
- pressed enter
- $
如果你执行了上面的脚本,它会等待输入换行符(\n)。按“enter”键以后,它会打印出“pressed enter”这个消息,然后退出。
2、使用“-i”选项交互地执行 expect 脚本
使用“-i”选项,可以通过来自于标准输入的读命令来交互地执行 expect 脚本。如下所示:
[plain] view plaincopyprint?
- $ 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 plaincopyprint?
- $ 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 plaincopyprint?
- $ expect -D 1 script
“-D”选项左边的选项会在调试器启动以前被处理。然后,在调试器启动以后,剩下的命令才会被执行。
[plain] view plaincopyprint?
- $ 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 plaincopyprint?
- $ expect -b
6、让 expect 不解释命令行参数
你可以使用标识符让 expect 不解释命令行参数。
你可以像下面这样的读入命令行参数:
[plain] view plaincopyprint?
- $ cat print_cmdline_args.exp
- #!/usr/bin/expect
- puts 'argv0 : [lindex $argv 0]';
- puts 'argv1 : [lindex $argv 1]';
当执行上面的脚本的时候,会跳过命令行选项,它们会被当成参数(而不是 expect 选项),如下所示:
[plain] view plaincopyprint?
- $ expect print_cmdline_args.exp -d -c
- argv0 : -d
- argv1 : -c
四、expect 简单例子
为了更好理解 except 脚本几个简单参数,我们再举一个简单的例子:
[plain] view plaincopyprint?
- #!/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 plaincopyprint?
- #!/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 plaincopyprint?
- #!/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 plaincopyprint?
- #!/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 plaincopyprint?
- 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 plaincopyprint?
- #!/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 plaincopyprint?
- 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 plaincopyprint?
- #!/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 plaincopyprint?
- 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 plaincopyprint?
- #!/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 plaincopyprint?
- 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 plaincopyprint?
- #!/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 plaincopyprint?
- #!/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 plaincopyprint?
- ./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 plaincopyprint?
- #!/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 plaincopyprint?
- ./batch_scp.sh ./hosts.list /root/src_file /root/destfile
用这两个脚本文件,就可以简单地完成批量 scp 的任务了。
六、综合例子
1、自动化脚本建立主机之间的 SSH 信任关系
[plain] view plaincopyprint?
- #!/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
自己的例子,登陆 sftp 并下载
cat sftp_download_file.exp
#!/usr/bin/expect
set keys [lindex $argv 0]
set hip "132.37.5.162"
set us "ftpnds01"
set pw "Linuxftpnds01_4321"
set dir "/INCOMING/BACKUP"
spawn sftp us@hip
expect {
"(yes/no)?" {send "yes\r"; expect_continue}
"password:" {send "$pw\r"}
}
expect "sftp>"
send "cd $dir\r"
expect "sftp>"
send "ls -r $keys\r"
expect "sftp>"
send "get $keys\r"
expect "sftp>"
send "bye\r"
expect eof
cat shell_expect_download_file.sh
#!/bin/bash
if [[ $1 == "" ]]
then
echo -e "\n 请输入关键字!\n"
exit;
else
keyw=$1;
fi
#登陆 sftp 并下载文件
./sftp_download_file.exp $keyw
#查看订单是否在文件里
echo "查看订单是否在文件里:"
grep $2 $keyw
cat sftp_download_file.exp
#!/usr/bin/expect
set keys1 [lindex $argv 0]
set keys2 [lindex $argv 1]
set hip "132.37.5.162"
set us "ftpnds01"
set pw "Linuxftpnds01_4321"
set dir "/INCOMING/BACKUP"
spawn sftp us@hip
expect {
"(yes/no)?" {send "yes\r"; expect_continue}
"password:" {send "$pw\r"}
}
expect "sftp>"
send "cd $dir\r"
expect "sftp>"
send "ls -r -C_ORDER_{keys1}*{keys2}\r"
expect "sftp>"
send "get -C_ORDER_{keys1}*{keys2}\r"
expect "sftp>"
send "bye\r"
expect eof
cat shell_expect_download_file.sh
#!/bin/bash
if [[ $1 == "" ]]
then
echo -e "\n 请输入订单号!\n"
exit;
else
keyw=$1;
fi
#登陆 sftp 并下载文件
#1816092211922553
pro=${keyw:0:2}
ordert=${keyw:2:6}
./sftp_download_file.exp pro ordert
#查看订单是否在文件里
echo "查看订单是否在文件里:"
grep $1 -C_ORDER_{pro}*{ordert}
cat shell_expect_download_file.sh
#!/bin/bash
if [[ $1 == "" ]]
then
echo -e "\n 请输入订单号!\n"
exit;
else
keyw=$1;
fi
pro=${keyw:0:2}
ordert=${keyw:2:6}
#如果参数 2 输入日期
if [[ $2 != "" ]]
then
ordert=${keyw:2:4}$2
fi
#登陆 sftp 并下载文件
./.sftp_download_file.exp pro ordert
#查看订单是否在文件里
#echo "查看订单是否在文件里:"
grep $1 -C_ORDER_{pro}*{ordert}
if [[ $? != 0 ]]
then
echo "订单未在同步文件里!!!!,请从新同步该订单或添加日期参数后从新查找,如 2016 年 9 月 23 号,写 23"
else
echo "该订单已在同步文件里!!!"
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于