expect 笔记

本贴最后更新于 2315 天前,其中的信息可能已经水流花落

Shell 脚本学习之 expect 命令

一、概述

我们通过 Shell 可以实现简单的控制流功能,如:循环、判断等。但是对于需要交互的场合则必须通过人工来干预,有时候我们可能会需要实现和交互程序如 telnet 服务器等进行交互的功能。而 expect 就使用来实现这种功能的工具。

   expect是一个免费的编程工具语言,用来实现自动和交互式任务进行通信,而无需人的干预。expect是不断发展的,随着时间的流逝,其功能越来越强大,已经成为系统管理员的的一个强大助手。expect需要Tcl编程语言的支持,要在系统上运行expect必须首先安装Tcl。

二、expect 的安装

expect 是在 Tcl 基础上创建起来的,所以在安装 expect 前我们应该先安装 Tcl。

(一)Tcl 安装

主页: http://www.tcl.tk

下载地址: https://nchc.dl.sourceforge.net/project/tcl/Tcl/8.4.20/tcl8.4.20-src.tar.gz

1.下载源码包

[plain] view plaincopyprint?

  1. wget http://nchc.dl.sourceforge.net/sourceforge/tcl/tcl8.4.11-src.tar.gz

2.解压缩源码包

[plain] view plaincopyprint?

  1. tar xfvz tcl8.4.11-src.tar.gz

3.安装配置

[plain] view plaincopyprint?

  1. cd tcl8.4.11/unix
  2. ./configure --prefix=/usr/tcl --enable-shared
  3. make
  4. make install

注意:

1、安装完毕以后,进入 tcl 源代码的根目录,把子目录 unix 下面的 tclUnixPort.h copy 到子目录 generic 中。

2、暂时不要删除 tcl 源代码,因为 expect 的安装过程还需要用。

(二)expect 安装 (需 Tcl 的库)

主页: http://expect.nist.gov/

1.下载源码包

[plain] view plaincopyprint?

  1. wget https://nchc.dl.sourceforge.net/project/expect/Expect/5.45.3/expect5.45.3.tar.gz

2.解压缩源码包

[plain] view plaincopyprint?

  1. tar xzvf expect5.45.tar.gz

3.安装配置

[plain] view plaincopyprint?

  1. cd expect5.45
  2. ./configure --prefix=/usr/expect --with-tcl=/usr/tcl/lib --with-tclinclude=../tcl8.4.11/generic
  3. make
  4. make install
  5. 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?

  1. Login: somebody Password: sillyme

Expect 最简单的脚本操作模式本质上和 Chat 脚本工作模式是一样的。

例子:

1、实现功能

下面我们分析一个响应 chsh 命令的脚本。我们首先回顾一下这个交互命令的格式。

假设我们要为用户 chavez 改变登录脚本,要求实现的命令交互过程如下:

[plain] view plaincopyprint?

  1. chsh chavez

  2. Changing the login shell for chavez
  3. Enter the new value, or press return for the default
  4. Login Shell [/bin/bash]: /bin/tcsh

可以看到该命令首先输出若干行提示信息并且提示输入用户新的登录 shell。我们必须在提示信息后面输入用户的登录 shell 或者直接回车不修改登录 shell。

2、实现自动执行

[plain] view plaincopyprint?

  1. #!/usr/bin/expect
  2. Change a login shell to tcsh

[plain] view plaincopyprint?

  1. set user [lindex $argv 0]
  2. spawn chsh $user
  3. expect "]:"
  4. send "/bin/tcsh "
  5. expect eof
  6. 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?

  1. expect -re "[(.*)]:"
  2. if {$expect_out(1,string)!="/bin/tcsh"} {
  3. send "/bin/tcsh" }
  4. send " "
  5. expect eof

说明:

(1)第一个 expect 命令现在使用了-re 参数,这个参数表示指定的的字符串是一个正则表达式,而不是一个普通的字符串。对于上面这个例子里是查找一个左方括号字符(其必须进行三次逃逸(escape),因此有三个符号,因为它对于 expect 和正则表达时来说都是特殊字符)后面跟有零个或多个字符,最后是一个右方括号字符。这里.*表示表示一个或多个任意字符,将其存放在()中是因为将匹配结果存放在一个变量中以实现随后的对匹配结果的访问。

(2)当发现一个匹配则检查包含在[]中的字符串,查看是否为/bin/tcsh。如果不是则发送/bin/tcsh 给 chsh 命令作为输入,如果是则仅仅发送一个回车符。这个简单的针对具体情况发出不同相响应的小例子说明了 expect 的强大功能。

(3)在一个正则表达时中,可以在()中包含若干个部分并通过 expect_out 数组访问它们。各个部分在表达式中从左到右进行编码,从 1 开始(0 包含有整个匹配输出)。()可能会出现嵌套情况,这这种情况下编码从最内层到最外层来进行的。

4、使用超时

下一个 expect 例子中将阐述具有超时功能的提示符函数。这个脚本提示用户输入,如果在给定的时间内没有输入,则会超时并返回一个默认的响应。这个脚本接收三个参数:提示符字串,默认响应和超时时间(秒)。

[plain] view plaincopyprint?

  1. #!/usr/bin/expect
  2. Prompt function with timeout and default.

  3. #脚本的第一部分首先是得到运行参数并将其保存到内部变量中
  4. set prompt [lindex $argv 0]
  5. set def [lindex $argv 1]
  6. set response $def
  7. set tout [lindex $argv 2]
  8. send_tty "$prompt: "
  9. #send_tty 命令用来实现在终端上显示提示符字串和一个冒号及空格
  10. set timeout $tout
  11. #set timeout 命令设置后面所有的 expect 命令的等待响应的超时时间为 $tout(-l 参数用来关闭任何超时设置)。
  12. expect " " {
  13. set raw $expect_out(buffer)
  14. remove final carriage return

  15. set response [string trimright "$raw" " "]
  16. }
  17. if {"response" == "} {set response def}
  18. send "$response "
  19. Prompt function with timeout and default.

  20. set prompt [lindex $argv 0]
  21. set def [lindex $argv 1]
  22. set response $def
  23. 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?

  1. % set a='prompt "Enter an answer" silence 10'
  2. Enter an answer: test
  3. % echo Answer was "$a"
  4. Answer was test

prompt 设定的超时为 10 秒。如果超时或者用户仅仅输入了回车符号,echo 命令将输出

[plain] view plaincopyprint?

  1. Answer was "silence"

5、一个更复杂的例子

下面我们将讨论一个更加复杂的 expect 脚本例子,这个脚本使用了一些更复杂的控制结构和很多复杂的交互过程。这个例子用来实现发送 write 命令给任意的用户,发送的消息来自于一个文件或者来自于键盘输入。

[plain] view plaincopyprint?

  1. #!/usr/bin/expect
  2. Write to multiple users from a prepared file

  3. or a message input interactively

  4. if {$argc<2} {
  5. send_user "usage: $argv0 file user1 user2 ... "
  6. exit
  7. }
  8. #send_user 命令用来显示使用帮助信息到父进程(一般为用户的 shell)的标准输出。
  9. set nofile 0
  10. get filename via the Tcl lindex function

  11. set file [lindex $argv 0]
  12. if {$file=="i"} {
  13. set nofile 1
  14. } else {
  15. make sure message file exists

  16. if {[file isfile $file]!=1} {
  17. send_user "argv0: file file not found. "
  18. exit }}
  19. ####################################################
  20. #(1)这部分实现处理脚本启动参数,其必须是一个储存要发送的消息的文件名或表示使用交互输入得到发送消的内容的"i"命令。
  21. #(2)变量 file 被设置为脚本的第一个参数的值,是通过一个 Tcl 函数 lindex 来实现的,该函数从列表/数组得到一个特定的元素。[]用来实现将函数 lindex 的返回值作为 set 命令的参数。
  22. #(3)如果脚本的第一个参数是小写的"i",那么变量 nofile 被设置为 1,否则通过调用 Tcl 的函数 isfile 来验证参数指定的文件存在,如果不存在就报错退出。
  23. #(4)可以看到这里使用了 if 命令来实现逻辑判断功能。该命令后面直接跟判断条件,并且执行在判断条件后的{}内的命令。if 条件为 false 时则运行 else 后的程序块。
  24. #######################################################
  25. set procs {}
  26. start write processes

  27. for {set i 1} {i<argc}
  28. {incr i} {
  29. spawn -noecho write
  30. [lindex argv i]
  31. lappend procs $spawn_id
  32. }
  33. #######################################################################################
  34. #(1)这一部分使用 spawn 命令来启动 write 进程实现向用户发送消息.
  35. #(2)这里使用了 for 命令来实现循环控制功能,循环变量首先设置为 1,然后因此递增。循环体是最后的{}的内容。
  36. #(3)这里我们是用脚本的第二个和随后的参数来 spawn 一个 write 命令,并将每个参数作为发送消息的用户名。
  37. #(4)lappend 命令使用保存每个 spawn 的进程的进程 ID 号的内部变量 $spawn_id 在变量 procs 中构造了一个进程 ID 号列表。
  38. ###################################################################################################
  39. if {$nofile==0} {
  40. setmesg [open "$file" "r"]
  41. } else {
  42. send_user "enter message,
  43. ending with ^D: " }
  44. #最后脚本根据变量 nofile 的值实现打开消息文件或者提示用户输入要发送的消息。
  45. set timeout -1
  46. while 1 {
  47. if {$nofile==0} {
  48. if {[gets $mesg chars] == -1} break
  49. set line "$chars "
  50. } else {
  51. expect_user {
  52. -re " " {}
  53. eof break }
  54. set line $expect_out(buffer) }
  55. foreach spawn_id $procs {
  56. send $line }
  57. sleep 1}
  58. exit
  59. ########################################################
  60. #(1)这段代码说明了实际的消息文本是如何通过无限循环 while 被发送的。
  61. #(2)while 循环中的 if 判断消息是如何得到的。在非交互模式下,下一行内容从消息文件中读出,当文件内容结束时 while 循环也就结束了。(break 命令实现终止循环) 。
  62. #(3)在交互模式下,expect_user 命令从用户接收消息,当用户输入 ctrl+D 时结束输入,循环同时结束。 两种情况下变量 $line 都被用来保存下一行消息内容。当是消息文件时,回车会被附加到消息的尾部。
  63. #(4)foreach 循环遍历 spawn 的所有进程,这些进程的 ID 号都保存在列表变量 $procs 中,实现分别和各个进程通信。send 命令组成了 foreach 的循环体,发送一行消息到当前的 write 进程。while 循环的最后是一个 sleep 命令,主要是用于处理非交互模式情况下,以确保消息 不会太快的发送给各个 write 进程。当 while 循环退出时,expect 脚本结束。
  64. ########################################################

四、使用 expect 脚本的小窍门

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

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

[plain] view plaincopyprint?

  1. $ expect -c 'expect "\n" {send "pressed enter\n"}
  2. pressed enter
  3. $

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

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

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

[plain] view plaincopyprint?

  1. $ expect -i arg1 arg2 arg3
  2. expect1.1>set argv
  3. arg1 arg2 arg3
  4. expect1.2>

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

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

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

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

[plain] view plaincopyprint?

  1. $ cat sample.exp
  2. !/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

  3. expect: does "" (spawn_id exp0) match glob pattern "\n"? no
  4. expect: does "\n" (spawn_id exp0) match glob pattern "\n"? yes
  5. expect: set expect_out(0,string) "\n"
  6. expect: set expect_out(spawn_id) "exp0"
  7. expect: set expect_out(buffer) "\n"
  8. send: sending "pressed enter" to { exp0 pressed enter}

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

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

[plain] view plaincopyprint?

  1. $ expect -D 1 script

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

[plain] view plaincopyprint?

  1. $ expect -c 'set timeout 10' -D 1 -c 'set a 1'
  2. 1: set a 1
  3. dbg1.0>

5、逐行地执行 expect 脚本

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

[plain] view plaincopyprint?

  1. $ expect -b

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

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

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

[plain] view plaincopyprint?

  1. $ cat print_cmdline_args.exp
  2. #!/usr/bin/expect
  3. puts 'argv0 : [lindex $argv 0]';
  4. puts 'argv1 : [lindex $argv 1]';

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

[plain] view plaincopyprint?

  1. $ expect print_cmdline_args.exp -d -c
  2. argv0 : -d
  3. argv1 : -c

四、expect 简单例子

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

[plain] view plaincopyprint?

  1. #!/usr/bin/expect
  2. set timeout 30
  3. spawn ssh -l username 192.168.1.1
  4. expect "password:"
  5. send "ispass\r"
  6. 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?

  1. #!/bin/bash
  2. ssh-keygen -t dsa
  3. for (( i = 1; i <= 100 ; i ++ ))
  4. do
  5. ./scp_key_to_node.exp $i
  6. done

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

[plain] view plaincopyprint?

  1. #!/usr/bin/expect
  2. set timeout 5
  3. set hostno [lindex $argv 0]
  4. spawn scp /.ssh/id_dsa.pub impala$hostno:/.ssh/pub_key
  5. expect "password"
  6. send "111111\r"
  7. spawn ssh impala$hostno "cat ~/.ssh/pub_key/ >> ~/.ssh/authorized_keys"
  8. expect "password"
  9. send "111111\r"
  10. spawn ssh impala$hostno "chmod 600 ~/.ssh/authorized_keys"
  11. expect "password"
  12. send "111111\r"
  13. expect eof

(3)分析:

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

spawn 是执行一个命令

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

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

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

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

(4)脚本执行方式:

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

./send_key.sh

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

[plain] view plaincopyprint?

  1. #!/usr/bin/expect -f
  2. set ip [lindex $argv 0 ] //接收第一个参数,并设置 IP
  3. set password [lindex $argv 1 ] //接收第二个参数,并设置密码
  4. set timeout 10 //设置超时时间
  5. spawn ssh root@$ip //发送 ssh 请滶
  6. expect { //返回信息匹配
  7. "*yes/no" { send "yes\r"; exp_continue} //第一次 ssh 连接会提示 yes/no,继续
  8. "*password:" { send "$password\r" } //出现密码提示,发送密码
  9. }
  10. interact //交互模式,用户会停留在远程服务器上面.

运行结果如下:

[plain] view plaincopyprint?

  1. root@ubuntu:/home/zhangy# ./test.exp 192.168.1.130 admin
  2. spawn ssh root@192.168.1.130
  3. Last login: Fri Sep 7 10:47:43 2012 from 192.168.1.142
  4. [root@linux ~]#

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

[plain] view plaincopyprint?

  1. #!/usr/bin/expect -f
  2. set ip 192.168.1.130
  3. set password admin
  4. set timeout 10
  5. spawn ssh root@$ip
  6. expect {
  7. "*yes/no" { send "yes\r"; exp_continue}
  8. "*password:" { send "$password\r" }
  9. }

运行结果如下:

[plain] view plaincopyprint?

  1. root@ubuntu:/home/zhangy# ./web.exp
  2. spawn ssh root@192.168.1.130
  3. Last login: Fri Sep 7 12:59:02 2012 from 192.168.1.142

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

[plain] view plaincopyprint?

  1. #!/usr/bin/expect -f
  2. set ip 192.168.1.130
  3. set password admin
  4. set timeout 10
  5. spawn ssh root@$ip
  6. expect {
  7. "*yes/no" { send "yes\r"; exp_continue}
  8. "*password:" { send "$password\r" }
  9. }
  10. expect "#*"
  11. send "pwd\r"
  12. send "exit\r"
  13. expect eof

运行结果如下:

[plain] view plaincopyprint?

  1. root@ubuntu:/home/zhangy# ./test3.exp
  2. spawn ssh root@192.168.1.130
  3. root@192.168.1.130's password:
  4. Last login: Fri Sep 7 14:05:07 2012 from 116.246.27.90
  5. [root@localhost ~]# pwd
  6. /root
  7. [root@localhost ~]# exit
  8. logout
  9. Connection to 192.168.1.130 closed.

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

[plain] view plaincopyprint?

  1. #!/usr/bin/expect -f
  2. set ip [lindex $argv 0 ]
  3. set dir [lindex $argv 1 ]
  4. set file [lindex $argv 2 ]
  5. set timeout 10
  6. spawn ftp $ip
  7. expect "Name*"
  8. send "zwh\r"
  9. expect "Password:*"
  10. send "zwh\r"
  11. expect "ftp>*"
  12. send "lcd $dir\r"
  13. expect {
  14. "*file" { send_user "local $_dir No such file or directory";send "quit\r" }
  15. "now" { send "get dir/file dir/file\r"}
  16. }
  17. expect {
  18. "*Failed" { send_user "remote $file No such file";send "quit\r" }
  19. "*OK" { send_user "$file has been download\r";send "quit\r"}
  20. }
  21. expect eof

运行结果如下:

[plain] view plaincopyprint?

  1. root@ubuntu:/home/zhangy# ./test2.exp 192.168.1.130 /var/www/www aaa.html
  2. spawn ftp 192.168.1.130
  3. Connected to 192.168.1.130.
  4. 220 (vsFTPd 2.0.5)
  5. Name (192.168.1.130:root): zwh
  6. 331 Please specify the password.
  7. Password:
  8. 230 Login successful.
  9. Remote system type is UNIX.
  10. Using binary mode to transfer files.
  11. ftp> lcd /var/www/www
  12. Local directory now /var/www/www
  13. ftp> get /var/www/www/aaa.html /var/www/www/aaa.html
  14. local: /var/www/www/aaa.html remote: /var/www/www/aaa.html
  15. 200 PORT command successful. Consider using PASV.
  16. 150 Opening BINARY mode data connection for /var/www/www/aaa.html (66 bytes).
  17. 226 File send OK.
  18. 66 bytes received in 0.00 secs (515.6 kB/s)
  19. quit aaa.html has been download
  20. 221 Goodbye.

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

[plain] view plaincopyprint?

  1. #!/bin/bash
  2. USER=mynameuser
  3. PASS=oldpassword
  4. NPASS=newpassword
  5. expect << EOF
  6. spawn passwd
  7. expect "Changing password for ${USER}."
  8. send "${PASS}\r"
  9. expect "Enter new UNIX password:"
  10. send "${NPASS}\r"
  11. expect "Retype new UNIX password:"
  12. send "${NPASS}\r"
  13. expect eof;
  14. EOF

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

[plain] view plaincopyprint?

  1. #!/usr/bin/expect
  2. set timeout 10
  3. set host [lindex $argv 0]
  4. set username [lindex $argv 1]
  5. set password [lindex $argv 2]
  6. set src_file [lindex $argv 3]
  7. set dest_file [lindex $argv 4]
  8. spawn scp src_file username@host:dest_file
  9. expect {
  10. "(yes/no)?"
  11. {  
    
  12.  send "yes\n"  
    
  13.  expect "*assword:" { send "$password\n"}  
    
  14. }
  15. "*assword:"
  16. {
  17. send "$password\n"
  18. }
  19. }
  20. expect "100%"
  21. expect eof

说明:

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

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

[plain] view plaincopyprint?

  1. ./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?

  1. #!/bin/sh
  2. list_file=$1
  3. src_file=$2
  4. dest_file=$3
  5. cat $list_file | while read line
  6. do
  7. host_ip=`echo $line | awk '{print $1}'`  
    
  8. username=`echo $line | awk '{print $2}'`  
    
  9. password=`echo $line | awk '{print $3}'`  
    
  10. echo "$host_ip"  
    
  11. ./expect_scp $host_ip $username $password $src_file $dest_file  
    
  12. done

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

IP username password

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

192.168.75.130 root 123456

192.168.75.131 knktc testpass

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

执行脚本:

[plain] view plaincopyprint?

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

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

六、综合例子

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

[plain] view plaincopyprint?

  1. #!/usr/bin/ksh
  2. #usage ./ssh_trust.sh host1 user1 passwd1 host2 user2 passwd2
  3. #即建立从 user1@host1 到 user2@host2 的 ssh 信任。
  4. src_host=$1
  5. src_username=$2
  6. src_passwd=$3
  7. dst_host=$4
  8. dst_username=$5
  9. dst_passwd=$6
  10. #在远程主机 1 上生成公私钥对
  11. Keygen()
  12. {
  13. expect << EOF
  14. spawn ssh src_username@src_host ssh-keygen -t rsa
  15. while 1 {
  16.      expect {  
    
  17.               "password:" {  
    
  18.                            send "$src_passwd\n"  
    
  19.                             }  
    
  20.              "yes/no*" {  
    
  21.                          send "yes\n"  
    
  22.                        }  
    
  23.                      "Enter file in which to save the key*" {  
    
  24.                                      send "\n"  
    
  25.                      }  
    
  26.                      "Enter passphrase*" {  
    
  27.                                      send "\n"  
    
  28.                      }  
    
  29.                      "Enter same passphrase again:" {  
    
  30.                                      send "\n"  
    
  31.                                      }  
    
  32.                      "Overwrite (y/n)" {  
    
  33.                                      send "n\n"  
    
  34.                      }  
    
  35.                      eof {  
    
  36.                                 exit  
    
  37.                      }  
    
  38.      }  
    
  39. }
  40. EOF
  41. }
  42. #从远程主机 1 获取公钥保存到本地
  43. Get_pub()
  44. {
  45. expect << EOF
  46. spawn scp src_username@src_host:~/.ssh/id_rsa.pub /tmp
  47. expect {
  48.           "password:" {  
    
  49.                         send "$src_passwd\n";exp_continue  
    
  50.              }  
    
  51.              "yes/no*" {  
    
  52.                         send "yes\n";exp_continue  
    
  53.              }     
    
  54.              eof {  
    
  55.                              exit  
    
  56.              }  
    
  57. }
  58. EOF
  59. }
  60. #将公钥的内容附加到远程主机 2 的 authorized_keys
  61. Put_pub()
  62. {
  63. src_pub="$(cat /tmp/id_rsa.pub)"
  64. expect << EOF
  65. spawn ssh dst_username@dst_host "mkdir -p ~/.ssh;echo $src_pub >> ~/.ssh/authorized_keys;chmod 600 ~/.ssh/authorized_keys"
  66. expect {
  67.          "password:" {  
    
  68.                      send "$dst_passwd\n";exp_continue  
    
  69.           }  
    
  70.          "yes/no*" {  
    
  71.                      send "yes\n";exp_continue  
    
  72.           }     
    
  73.          eof {  
    
  74.                      exit  
    
  75.           }   
    
  76. }
  77. EOF
  78. }
  79. Keygen
  80. Get_pub
  81. Put_pub

来自 [http://www.cnblogs.com/lixigang/articles/4849527.html](http://www.cnblogs.com/lixigang/articles/4849527.html)

自己的例子,登陆 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 "该订单已在同步文件里!!!"

相关帖子

回帖

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • Postman

    Postman 是一款简单好用的 HTTP API 调试工具。

    4 引用 • 3 回帖 • 7 关注
  • ZooKeeper

    ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 HBase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

    59 引用 • 29 回帖 • 14 关注
  • 学习

    “梦想从学习开始,事业从实践起步” —— 习近平

    171 引用 • 512 回帖
  • JWT

    JWT(JSON Web Token)是一种用于双方之间传递信息的简洁的、安全的表述性声明规范。JWT 作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以 JSON 的形式安全的传递信息。

    20 引用 • 15 回帖 • 6 关注
  • C++

    C++ 是在 C 语言的基础上开发的一种通用编程语言,应用广泛。C++ 支持多种编程范式,面向对象编程、泛型编程和过程化编程。

    107 引用 • 153 回帖
  • Love2D

    Love2D 是一个开源的, 跨平台的 2D 游戏引擎。使用纯 Lua 脚本来进行游戏开发。目前支持的平台有 Windows, Mac OS X, Linux, Android 和 iOS。

    14 引用 • 53 回帖 • 538 关注
  • 宕机

    宕机,多指一些网站、游戏、网络应用等服务器一种区别于正常运行的状态,也叫“Down 机”、“当机”或“死机”。宕机状态不仅仅是指服务器“挂掉了”、“死机了”状态,也包括服务器假死、停用、关闭等一些原因而导致出现的不能够正常运行的状态。

    13 引用 • 82 回帖 • 59 关注
  • 机器学习

    机器学习(Machine Learning)是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。

    83 引用 • 37 回帖
  • Swagger

    Swagger 是一款非常流行的 API 开发工具,它遵循 OpenAPI Specification(这是一种通用的、和编程语言无关的 API 描述规范)。Swagger 贯穿整个 API 生命周期,如 API 的设计、编写文档、测试和部署。

    26 引用 • 35 回帖 • 5 关注
  • 房星科技

    房星网,我们不和没有钱的程序员谈理想,我们要让程序员又有理想又有钱。我们有雄厚的房地产行业线下资源,遍布昆明全城的 100 家门店、四千地产经纪人是我们坚实的后盾。

    6 引用 • 141 回帖 • 584 关注
  • App

    App(应用程序,Application 的缩写)一般指手机软件。

    91 引用 • 384 回帖 • 2 关注
  • 自由行
    4 关注
  • Sym

    Sym 是一款用 Java 实现的现代化社区(论坛/BBS/社交网络/博客)系统平台。

    下一代的社区系统,为未来而构建

    524 引用 • 4601 回帖 • 700 关注
  • iOS

    iOS 是由苹果公司开发的移动操作系统,最早于 2007 年 1 月 9 日的 Macworld 大会上公布这个系统,最初是设计给 iPhone 使用的,后来陆续套用到 iPod touch、iPad 以及 Apple TV 等产品上。iOS 与苹果的 Mac OS X 操作系统一样,属于类 Unix 的商业操作系统。

    85 引用 • 139 回帖
  • Log4j

    Log4j 是 Apache 开源的一款使用广泛的 Java 日志组件。

    20 引用 • 18 回帖 • 29 关注
  • TensorFlow

    TensorFlow 是一个采用数据流图(data flow graphs),用于数值计算的开源软件库。节点(Nodes)在图中表示数学操作,图中的线(edges)则表示在节点间相互联系的多维数据数组,即张量(tensor)。

    20 引用 • 19 回帖 • 1 关注
  • NGINX

    NGINX 是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 NGINX 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,第一个公开版本 0.1.0 发布于 2004 年 10 月 4 日。

    313 引用 • 547 回帖 • 1 关注
  • Hprose

    Hprose 是一款先进的轻量级、跨语言、跨平台、无侵入式、高性能动态远程对象调用引擎库。它不仅简单易用,而且功能强大。你无需专门学习,只需看上几眼,就能用它轻松构建分布式应用系统。

    9 引用 • 17 回帖 • 612 关注
  • SVN

    SVN 是 Subversion 的简称,是一个开放源代码的版本控制系统,相较于 RCS、CVS,它采用了分支管理系统,它的设计目标就是取代 CVS。

    29 引用 • 98 回帖 • 694 关注
  • Latke

    Latke 是一款以 JSON 为主的 Java Web 框架。

    71 引用 • 535 回帖 • 789 关注
  • WordPress

    WordPress 是一个使用 PHP 语言开发的博客平台,用户可以在支持 PHP 和 MySQL 数据库的服务器上架设自己的博客。也可以把 WordPress 当作一个内容管理系统(CMS)来使用。WordPress 是一个免费的开源项目,在 GNU 通用公共许可证(GPLv2)下授权发布。

    66 引用 • 114 回帖 • 223 关注
  • Solo

    Solo 是一款小而美的开源博客系统,专为程序员设计。Solo 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    1435 引用 • 10056 回帖 • 489 关注
  • CSDN

    CSDN (Chinese Software Developer Network) 创立于 1999 年,是中国的 IT 社区和服务平台,为中国的软件开发者和 IT 从业者提供知识传播、职业发展、软件开发等全生命周期服务,满足他们在职业发展中学习及共享知识和信息、建立职业发展社交圈、通过软件开发实现技术商业化等刚性需求。

    14 引用 • 155 回帖
  • Git

    Git 是 Linux Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。

    209 引用 • 358 回帖 • 1 关注
  • 分享

    有什么新发现就分享给大家吧!

    248 引用 • 1795 回帖
  • 百度

    百度(Nasdaq:BIDU)是全球最大的中文搜索引擎、最大的中文网站。2000 年 1 月由李彦宏创立于北京中关村,致力于向人们提供“简单,可依赖”的信息获取方式。“百度”二字源于中国宋朝词人辛弃疾的《青玉案·元夕》词句“众里寻他千百度”,象征着百度对中文信息检索技术的执著追求。

    63 引用 • 785 回帖 • 164 关注
  • BookxNote

    BookxNote 是一款全新的电子书学习工具,助力您的学习与思考,让您的大脑更高效的记忆。

    笔记整理交给我,一心只读圣贤书。

    1 引用 • 1 回帖