Shell脚本学习之expect命令

一、概述

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

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

二、expect的安装

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

(一)Tcl 安装

主页:http://www.tcl.tk
下载地址:http://www.tcl.tk/software/tcltk/downloadnow84.tml
1.下载源码包

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


2.解压缩源码包

  1. tarxfvztcl8.4.11-src.tar.gz


3.安装配置

  1. cdtcl8.4.11/unix

  2. ./configure--prefix=/usr/tcl--enable-shared

  3. make

  4. makeinstall

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

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

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

主页:http://expect.nist.gov/
1.下载源码包

  1. wgethttp://sourceforge.net/projects/expect/files/Expect/5.45/expect5.45.tar.gz/download


2.解压缩源码包

  1. tarxzvfexpect5.45.tar.gz


3.安装配置

  1. cdexpect5.45

  2. ./configure--prefix=/usr/expect--with-tcl=/usr/tcl/lib--with-tclinclude=../tcl8.4.11/generic

  3. make

  4. makeinstall

  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。

引用:

  1. Login:somebodyPassword:sillyme

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

例子:

1、实现功能

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

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

  1. #chshchavez

  2. Changingtheloginshellforchavez

  3. Enterthenewvalue,orpressreturnforthedefault

  4. LoginShell[/bin/bash]:/bin/tcsh

  5. #

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


2、实现自动执行

  1. #!/usr/bin/expect

  2. #Changealoginshelltotcsh

  1. setuser[lindex$argv0]

  2. spawnchsh$user

  3. expect"]:"

  4. send"/bin/tcsh"

  5. expecteof

  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例子:

  1. expect-re"\[(.*)]:"

  2. if{$expect_out(1,string)!="/bin/tcsh"}{

  3. send"/bin/tcsh"}

  4. send""

  5. expecteof

说明:

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

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

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

4、使用超时

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

  1. #!/usr/bin/expect

  2. #Promptfunctionwithtimeoutanddefault.

  3. #脚本的第一部分首先是得到运行参数并将其保存到内部变量中

  4. setprompt[lindex$argv0]

  5. setdef[lindex$argv1]

  6. setresponse$def

  7. settout[lindex$argv2]

  8. send_tty"$prompt:"

  9. #send_tty命令用来实现在终端上显示提示符字串和一个冒号及空格

  10. settimeout$tout

  11. #settimeout命令设置后面所有的expect命令的等待响应的超时时间为$tout(-l参数用来关闭任何超时设置)。

  12. expect""{

  13. setraw$expect_out(buffer)

  14. #removefinalcarriagereturn

  15. setresponse[stringtrimright"$raw"""]

  16. }

  17. if{"$response"=="}{setresponse$def}

  18. send"$response"

  19. #Promptfunctionwithtimeoutanddefault.

  20. setprompt[lindex$argv0]

  21. setdef[lindex$argv1]

  22. setresponse$def

  23. settout[lindex$argv2]


说明:

(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中。

  1. %seta='prompt"Enterananswer"silence10'

  2. Enterananswer:test

  3. %echoAnswerwas"$a"

  4. Answerwastest

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

  1. Answerwas"silence"

5、一个更复杂的例子

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

  1. #!/usr/bin/expect

  2. #Writetomultipleusersfromapreparedfile

  3. #oramessageinputinteractively

  4. if{$argc<2}{

  5. send_user"usage:$argv0fileuser1user2..."

  6. exit

  7. }

  8. #send_user命令用来显示使用帮助信息到父进程(一般为用户的shell)的标准输出。

  9. setnofile0

  10. #getfilenameviatheTcllindexfunction

  11. setfile[lindex$argv0]

  12. if{$file=="i"}{

  13. setnofile1

  14. }else{

  15. #makesuremessagefileexists

  16. if{[fileisfile$file]!=1}{

  17. send_user"$argv0:file$filenotfound."

  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. setprocs{}

  26. #startwriteprocesses

  27. for{seti1}{$i<$argc}

  28. {incri}{

  29. spawn-noechowrite

  30. [lindex$argv$i]

  31. lappendprocs$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"entermessage,

  43. endingwith^D:"}

  44. #最后脚本根据变量nofile的值实现打开消息文件或者提示用户输入要发送的消息。

  45. settimeout-1

  46. while1{

  47. if{$nofile==0}{

  48. if{[gets$mesgchars]==-1}break

  49. setline"$chars"

  50. }else{

  51. expect_user{

  52. -re""{}

  53. eofbreak}

  54. setline$expect_out(buffer)}

  55. foreachspawn_id$procs{

  56. send$line}

  57. sleep1}

  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”选项,直接在命令行中执行它,如下所示:

  1. $expect-c'expect"\n"{send"pressedenter\n"}

  2. pressedenter

  3. $

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

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

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

  1. $expect-iarg1arg2arg3

  2. expect1.1>setargv

  3. arg1arg2arg3

  4. expect1.2>

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

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

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

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

  1. $catsample.exp

  2. #!/usr/bin/expect-fexpect"\n";send"pressedenter";$expect-dsample.expexpectversion5.43.0argv[0]=expectargv[1]=-dargv[2]=sample.expsetargc0setargv0"sample.exp"setargv""executingcommandsfromcommandfilesample.exp

  3. expect:does""(spawn_idexp0)matchglobpattern"\n"?no

  4. expect:does"\n"(spawn_idexp0)matchglobpattern"\n"?yes

  5. expect:setexpect_out(0,string)"\n"

  6. expect:setexpect_out(spawn_id)"exp0"

  7. expect:setexpect_out(buffer)"\n"

  8. send:sending"pressedenter"to{exp0pressedenter}

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

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

  1. $expect-D1script

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

  1. $expect-c'settimeout10'-D1-c'seta1'

  2. 1:seta1

  3. dbg1.0>

5、逐行地执行expect脚本

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

  1. $expect-b

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

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

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

  1. $catprint_cmdline_args.exp

  2. #!/usr/bin/expect

  3. puts'argv0:[lindex$argv0]';

  4. puts'argv1:[lindex$argv1]';

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

  1. $expectprint_cmdline_args.exp-d-c

  2. argv0:-d

  3. argv1:-c

四、expect简单例子

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

  1. #!/usr/bin/expect

  2. settimeout30

  3. spawnssh-lusername192.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

  1. #!/bin/bash

  2. ssh-keygen-tdsa

  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)

  1. #!/usr/bin/expect

  2. settimeout5

  3. sethostno[lindex$argv0]

  4. spawnscp~/.ssh/id_dsa.pubimpala$hostno:~/.ssh/pub_key

  5. expect"*password*"

  6. send"111111\r"

  7. spawnsshimpala$hostno"cat~/.ssh/pub_key/>>~/.ssh/authorized_keys"

  8. expect"*password*"

  9. send"111111\r"

  10. spawnsshimpala$hostno"chmod600~/.ssh/authorized_keys"

  11. expect"*password*"

  12. send"111111\r"

  13. expecteof

(3)分析:
set可以设置超时,或者设置一个变量的值
spawn是执行一个命令
expect等待一个匹配的输出流中的内容
send是匹配到之后向输入流写入的内容
[lindex $argv 0]表示脚本的第0个参数
expect eof表示读取到文件结束符
(4)脚本执行方式:
在脚本所在的目录下执行:
# ./send_key.sh

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

  1. #!/usr/bin/expect-f

  2. setip[lindex$argv0]//接收第一个参数,并设置IP

  3. setpassword[lindex$argv1]//接收第二个参数,并设置密码

  4. settimeout10//设置超时时间

  5. spawnsshroot@$ip//发送ssh请�E

  6. expect{//返回信息匹配

  7. "*yes/no"{send"yes\r";exp_continue}//第一次ssh连接会提示yes/no,继续

  8. "*password:"{send"$password\r"}//出现密码提示,发送密码

  9. }

  10. interact//交互模式,用户会停留在远程服务器上面.

运行结果如下:

  1. root@ubuntu:/home/zhangy#./test.exp192.168.1.130admin

  2. spawnsshroot@192.168.1.130

  3. Lastlogin:FriSep710:47:432012from192.168.1.142

  4. [root@linux~]#


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

  1. #!/usr/bin/expect-f

  2. setip192.168.1.130

  3. setpasswordadmin

  4. settimeout10

  5. spawnsshroot@$ip

  6. expect{

  7. "*yes/no"{send"yes\r";exp_continue}

  8. "*password:"{send"$password\r"}

  9. }

运行结果如下:

  1. root@ubuntu:/home/zhangy#./web.exp

  2. spawnsshroot@192.168.1.130

  3. Lastlogin:FriSep712:59:022012from192.168.1.142


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

  1. #!/usr/bin/expect-f

  2. setip192.168.1.130

  3. setpasswordadmin

  4. settimeout10

  5. spawnsshroot@$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. expecteof

运行结果如下:

  1. root@ubuntu:/home/zhangy#./test3.exp

  2. spawnsshroot@192.168.1.130

  3. root@192.168.1.130'spassword:

  4. Lastlogin:FriSep714:05:072012from116.246.27.90

  5. [root@localhost~]#pwd

  6. /root

  7. [root@localhost~]#exit

  8. logout

  9. Connectionto192.168.1.130closed.

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

  1. #!/usr/bin/expect-f

  2. setip[lindex$argv0]

  3. setdir[lindex$argv1]

  4. setfile[lindex$argv2]

  5. settimeout10

  6. spawnftp$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$_dirNosuchfileordirectory";send"quit\r"}

  15. "*now*"{send"get$dir/$file$dir/$file\r"}

  16. }

  17. expect{

  18. "*Failed"{send_user"remote$fileNosuchfile";send"quit\r"}

  19. "*OK"{send_user"$filehasbeendownload\r";send"quit\r"}

  20. }

  21. expecteof

运行结果如下:

  1. root@ubuntu:/home/zhangy#./test2.exp192.168.1.130/var/www/wwwaaa.html

  2. spawnftp192.168.1.130

  3. Connectedto192.168.1.130.

  4. 220(vsFTPd2.0.5)

  5. Name(192.168.1.130:root):zwh

  6. 331Pleasespecifythepassword.

  7. Password:

  8. 230Loginsuccessful.

  9. RemotesystemtypeisUNIX.

  10. Usingbinarymodetotransferfiles.

  11. ftp>lcd/var/www/www

  12. Localdirectorynow/var/www/www

  13. ftp>get/var/www/www/aaa.html/var/www/www/aaa.html

  14. local:/var/www/www/aaa.htmlremote:/var/www/www/aaa.html

  15. 200PORTcommandsuccessful.ConsiderusingPASV.

  16. 150OpeningBINARYmodedataconnectionfor/var/www/www/aaa.html(66bytes).

  17. 226FilesendOK.

  18. 66bytesreceivedin0.00secs(515.6kB/s)

  19. quitaaa.htmlhasbeendownload

  20. 221Goodbye.

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

  1. #!/bin/bash

  2. USER=mynameuser

  3. PASS=oldpassword

  4. NPASS=newpassword

  5. expect<<EOF

  6. spawnpasswd

  7. expect"Changingpasswordfor${USER}."

  8. send"${PASS}\r"

  9. expect"EnternewUNIXpassword:"

  10. send"${NPASS}\r"

  11. expect"RetypenewUNIXpassword:"

  12. send"${NPASS}\r"

  13. expecteof;

  14. EOF

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

  1. #!/usr/bin/expect

  2. settimeout10

  3. sethost[lindex$argv0]

  4. setusername[lindex$argv1]

  5. setpassword[lindex$argv2]

  6. setsrc_file[lindex$argv3]

  7. setdest_file[lindex$argv4]

  8. spawnscp$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. expecteof

说明:

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

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

  1. ./expect_scp192.168.75.130root123456/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脚本。

  1. #!/bin/sh

  2. list_file=$1

  3. src_file=$2

  4. dest_file=$3

  5. cat$list_file|whilereadline

  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在执行时会输入错误的密码。

执行脚本:

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

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

六、综合例子

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

  1. #!/usr/bin/ksh

  2. #usage./ssh_trust.shhost1user1passwd1host2user2passwd2

  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. spawnssh$src_username@$src_hostssh-keygen-trsa

  15. while1{

  16. expect{

  17. "password:"{

  18. send"$src_passwd\n"

  19. }

  20. "yes/no*"{

  21. send"yes\n"

  22. }

  23. "Enterfileinwhichtosavethekey*"{

  24. send"\n"

  25. }

  26. "Enterpassphrase*"{

  27. send"\n"

  28. }

  29. "Entersamepassphraseagain:"{

  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. spawnscp$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. spawnssh$dst_username@$dst_host"mkdir-p~/.ssh;echo$src_pub>>~/.ssh/authorized_keys;chmod600~/.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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


用的openwrt路由器,家里宽带申请了动态公网ip,为了方便把2280端口映射到公网,发现经常被暴力破解,自己写了个临时封禁ip功能的脚本,实现5分钟内同一个ip登录密码错误10次就封禁这个ip5分钟,并且进行邮件通知使用步骤openwrt为19.07.03版本,其他版本没有测试过安装bashmsmtpopkg
#!/bin/bashcommand1&command2&wait从Shell脚本并行运行多个程序–杨河老李(kviccn.github.io)
1.先查出MAMP下面集成的PHP版本cd/Applications/MAMP/bin/phpls-ls 2.编辑修改.bash_profile文件(没有.bash_profile文件的情况下回自动创建)sudovim~/.bash_profile在文件的最后输入以下信息,然后保存退出exportPATH="/Applications/MAMP/bin/php/php7.2.20/b
1、先输入locale-a,查看一下现在已安装的语言2、若不存在如zh_CN之类的语言包,进行中文语言包装:apt-getinstalllanguage-pack-zh-hans3、安装好后我们可以进行临时修改:然后添加中文支持: locale-genzh_CN.UTF-8临时修改> export LC_ALL='zh_CN.utf8'> locale永久
BashPerlTclsyntaxdiff1.进制数表示Languagebinaryoctalhexadecimalbash2#[0~1]0[0~7]0x[0~f]or0X[0~f]perl0b[0~1]0[0~7]0x[0~f]tcl0b[0~1]0o[0~7]0x[0~f]bashdifferentbaserepresntationreference2.StringlengthLanguageStr
正常安装了k8s后,使用kubect工具后接的命令不能直接tab补全命令补全方法:yum-yinstallbash-completionsource/usr/share/bash-completion/bash_completionsource<(kubectlcompletionbash)echo"source<(kubectlcompletionbash)">>~/.bashrc 
参考这里启动jar包shell脚本修改过来的#!/bin/bash#默认应用名称defaultAppName='./gadmin'appName=''if[[$1&&$1!=0]]thenappName=$1elseappName=$defaultAppNamefiecho">>>>>>本次重启的应用:$appName<
#一个数字的行#!/bin/bashwhilereadlinedon=`echo$line|sed's/[^0-9]//g'|wc-L`if[$n-eq1]thenecho$linefidone<1.txt#日志切割归档#!/bin/bashcd/data/logslog=1.logmv_log(){[-f$1]&&mv$1$2
#文件增加内容#!/bin/bashn=0cat1.txt|whilereadlinedon=[$n+1]if[$n-eq5]thenecho$lineecho-e"#Thisisatestfile.\n#Testinsertlineintothisfile."elseecho$linefidone#备份/etc目录#
# su - oraclesu: /usr/bin/ksh: No such file or directory根据报错信息:显示无法找到文件 /usr/bin/ksh果然没有该文件,但是发现存在文件/bin/ksh,于是创建了一个软连接,可以规避问题,可以成功切换到用户下,但无法执行系统自带命令。$. .bash_profile-ksh: .: .b
history显示历史指令记录内容,下达历史纪录中的指令主要的使用方法如果你想禁用history,可以将HISTSIZE设置为0:#exportHISTSIZE=0使用HISTIGNORE忽略历史中的特定命令下面的例子,将忽略pwd、ls、ls-ltr等命令:#exportHISTIGNORE=”pwd:ls:ls-ltr:”使用HIS
一.命令历史  1.history环境变量:    HISTSIZE:输出的命令历史条数,如history的记录数    HISTFILESIZE:~/.bash_history保存的命令历史记录数    HISTFILLE:历史记录的文件路径    HISTCONTROL:     ignorespace:忽略以空格开头的命令
之前在网上看到很多师傅们总结的linux反弹shell的一些方法,为了更熟练的去运用这些技术,于是自己花精力查了很多资料去理解这些命令的含义,将研究的成果记录在这里,所谓的反弹shell,指的是我们在自己的机器上开启监听,然后在被攻击者的机器上发送连接请求去连接我们的机器,将被攻击者的she
BashOne-LinersExplained,PartI:Workingwithfileshttps://catonmat.net/bash-one-liners-explained-part-oneBashOne-LinersExplained,PartII:Workingwithstringshttps://catonmat.net/bash-one-liners-explained-part-twoBashOne-LinersExplained,PartII
Shell中变量的作用域:在当前Shell会话中使用,全局变量。在函数内部使用,局部变量。可以在其他Shell会话中使用,环境变量。局部变量:默认情况下函数内的变量也是全局变量#!/bin/bashfunctionfunc(){a=99}funcecho$a输出>>99为了让全局变量变成局部变量
1、多命令顺序执行;  命令1;命令2  多个命令顺序执行,命令之间没有任何逻辑联系&&  命令1&&命令2  逻辑与,当命令1正确执行,才会执行命令2||  命令1||命令2  逻辑或,当命令1执行不正确,才会执行命令2例如:ls;date;cd/home/lsx;pwd;who ddif=输入文件of=输
原博文使用Linux或者unix系统的同学可能都对#!这个符号并不陌生,但是你真的了解它吗?首先,这个符号(#!)的名称,叫做"Shebang"或者"Sha-bang"。Linux执行文件时发现这个格式,会把!后的内容提取出来拼接在脚本文件或路径之前,当作实际执行的命令。 Shebang这个符号通常在Unix系统的脚本
1、历史命令history[选项][历史命令保存文件]选项:-c:  清空历史命令-w:  把缓存中的历史命令写入历史命令保存文件 ~/.bash_historyvim/etc/profile中的Histsize可改存储历史命令数量历史命令的调用使用上、下箭头调用以前的历史命令使用“!n”重复执行第n条历史
目录1.Shell脚本规范2.Shell脚本执行3.Shell脚本变量3.1环境变量3.1.1自定义环境变量3.1.2显示与取消环境变量3.1.3环境变量初始化与对应文件的生效顺序3.2普通变量3.2.1定义本地变量3.2.2shell调用变量3.2.3grep调用变量3.2.4awk调用变量3.3
   http://www.voidcn.com/blog/wszzdanm/article/p-6145895.html命令功能:显示登录用户的信息命令格式:常用选项:举例:w显示已经登录的用户及正在进行的操作[root@localhost~]#w 11:22:01up4days,21:22, 3users, loadaverage:0.00,0.00,0.00USER