Shell-通过expect工具脚本的自动交互及实例
安装expect工具
expect是建立在tcl基础上的一个自动化交互套件, 在一些需要交互输入指令的场景下, 可通过脚本设置自动进行交互通信. 其交互流程是:
spawn启动指定进程 -> expect获取指定关键字 -> send想指定进程发送指定指令 -> 执行完成, 退出.
由于expect是基于tcl的, 所以需要确保系统中安装了tcl:
# 检查是否安装了tcl: [root@localhost ~]# whereis tcl tcl: /usr/lib64/tcl8.5 /usr/include/tcl.h /usr/share/tcl8.5 # 如果没有安装, 使用yum安装tcl和expect: [root@localhost ~]# yum install -y tcl [root@localhost ~]# yum install -y expect # 查看expect的安装路径: [root@localhost ~]# command -v expect /usr/bin/expect
expect的常用命令
命 令 说 明 spawn 启动新的交互进程, 后面跟命令或者指定程序 expect 从进程中接收信息, 如果匹配成功, 就执行expect后的动作 send 向进程发送字符串 send exp_send 用于发送指定的字符串信息 exp_continue 在expect中多次匹配就需要用到 send_user 用来打印输出 相当于shell中的echo interact 允许用户交互 exit 退出expect脚本 eof expect执行结束, 退出 set 定义变量 puts 输出变量 set timeout 设置超时时间
- Expect中最关键的四个命令是send,expect,spawn,interact。
简单的用法,稍作修改就可以变成很实用都脚本。
#!/usr/bin/expect -f #用expect实现自动交互 #期望的字符串必须出现,而且必须按顺序出现,send记得带上换行符 #指定要启动的命令,可以带参数 spawn ./hello.sh v1 v2 #可以设置超时,默认的超时就是10秒 set timeout 11 #一问一答的节奏,expct期望一个字符串,然后send回应一个字符串 expect "(yes/no)" send "yes\r" expect "password:" send "no-password\n" #期望的字符串是正则匹配的,发送要记得换行符,\r和\n都行 expect "*num" send "1\n" #等着命令结束后退出 expect eof
例1:远程登录并创建文件后退出
#!/usr/bin/expect ##注意路径,使用 [whereis expect] 查看 set user "hadoop" ##设定参数,注意",'的区别 set pwd "yangkun" set host "48.93.36.144" set timeout -1 ##;号可有可无 spawn ssh -p 2020 $user@$host expect { ##expect后有空格 "*yes/no" {send "yes\r";exp_continue} "*password:" {send "$pwd\r"} } expect "]*" ## 通配符,使用 ]* 有效, 使用 *# 无效 send "touch /home/hadoop/aa.txt\r" expect "]*" send "echo hello world >> /home/hadoop/aa.txt\r" expect "]*" [interact] ##人为交互 send "exit\r" ##退出
例2:配置免密登录并安装JDK
#!/bin/bash #!/usr/bin/expect SERVERS="114.114.114.114" ##数组以空格分隔,可以为目标ip 或者hostName PASSWORD="yangkun" ## 实现免密登录配置的函数 auto_ssh_copy_id() { expect -c "set timeout -1; spawn ssh-copy-id \"-p 2020 $1\"; ## 这里要注意,使用'或\'不可行 expect { *(yes/no)* {send -- yes\r;exp_continue;} *password:* {send -- $2\r;exp_continue;} eof {exit 0;} }"; } ## 循环执行,配置主机到从节点所有免密 ssh_copy_id_to_all() { for SERVER in $SERVERS ## 取值需要加$ do auto_ssh_copy_id $SERVER $PASSWORD done } ## 调用循环配置函数 ssh_copy_id_to_all ## 批量部署 for SERVER in $SERVERS do scp install.sh root@$SERVER:/root ssh root@$SERVER /root/install.sh donel取文件中的host配置
读取文件中的host配置
让脚本自动读取slaves文件中的机器名来批量安装 cat slaves | while read host do echo $host expect -c "set timeout -f spawn ssh-copy-id $host" done
例3:批量配置JDK,install.sh
以root用户执行
#!/bin/bash BASE_SERVER=master BASE_PATH=/home/hadoop/soft TARGET_PATH=/usr/local JAVA_PATH=$TARGET_PATH/java ## 1.判断是否存在文件夹,不存在则创建soft文件夹 #if [ ! -d "$BASE_PATH" ]; then # mkdir "$BASE_PATH" #fi ## 2.从指定host拷贝jdk到目标机器上(已经拷贝文件夹) scp -r $BASE_SERVER:$BASE_PATH $BASE_PATH ## 2.解压jdk到指定目录 if [ ! -d "$JAVA_PATH" ]; then sudo -S mkdir -p "$JAVA_PATH" fi ## 赋予权限 sudo -S chmod -R hadoop:hadoop $JAVA_PATH tar -zxvf $BASE_PATH/jdk1.8.0_121.tar.gz -C $JAVA_PATH ## 3.配置环境变量 sudo -S cat>>/etc/profile<<EOF export JAVA_HOME=$JAVA_PATH/jdk1.8.0_121 export PATH=\$PATH:\$JAVA_HOME/bin EOF
代码保存为一个脚本,例如 a.sh,加上执行执行权限后 ./a.sh 即可运行。
下面接受作用原理简介
简单的示例脚本
这里以ssh远程登录某台服务器的脚本为例进行说明, 假设此脚本名称为remote_login.sh
:
#!/usr/bin/expect set timeout 30 spawn ssh -l root 172.16.22.131 expect "password*" send "123456\r" interact
脚本功能解读
(1) #!/usr/bin/expect
上述内容必须位于脚本文件的第一行, 用来告诉操作系统, 此脚本需要使用系统的哪个脚本解析引擎来执行.
具体路径可通过command -v expect
命令查看.
注意: 这里的expect和Linux的bash、Windows的cmd等程序一样, 都是一种脚本执行引擎. 脚本需要有可执行权限(chmod +x remote_login.sh, 或chmod 755 auto_login.sh), 然后通过命令./remote_login.sh运行即可; 不行的话尝试:expect remote_login.sh 方式运行。 如果输入sh remote_login.sh, 意义就不一样了: 明确调用sh引擎去执行此脚本, 此时首行的#!/usr/bin/expect就失效了,所以也可以直接输入 expect remote_login.sh 运行。
(2) set timeout 30
设置连接的超时时间为30秒.
(3) spawn ssh -l root 172.16.22.131
spawn是expect的语句,执行命令前都要加这句,简单地理解就是,在你原来要执行的shell命令前面多加一个 spawn 关键字;
spawn、send等命令是expect工具中的内部命令, 如果没有安装expect工具, 就会出现"spawn not found"等错误.
不要用which spawn之类的命令去找spawn, 因为并没有这样的程序.
(4) expect "password*"
这个命令用来判断上次命令执行的输出结果里是否包含"password*"的字符串;只有spawn执行的命令结果才会被expect捕捉到,因为spawn会启动一个进程,只有这个进程的相关信息才会被捕捉到,主要包括:标准输入的提示信息,eof和timeout;如果捕获到则立即返回, 否则就等待一段时间后返回. 这里的等待时长就是前面设置的timeout, 也就是30秒.
(5) send "123456\r"
这里就是执行交互动作, 作用等同于手工输入密码。send会将expect脚本中需要的信息发送给spawn启动的那个进程,而send_user只是回显用户发出的信息,类似于shell中的echo而已
提示: 命令字符串结尾加上\r, 这样的话, 如果出现异常等待的状态就能够停留下来, 作进一步的核查.
(6) interact
interact代表执行完留在远程控制台,不加这句执行完后返回本地控制台 。expect执行完成后保持用户的交互状态, 这个时候用户就可以手工操作了.
如果没有这一句, expect执行完成后就会退出脚本刚刚远程登录过去的终端, 用户也就不能继续操作了.
其他脚本使用示例
4.1 直接通过expect执行多条命令
注意首行内容, 这种情况下就只能通过./script.sh(或
expect script.sh)
来执行这类脚本了:
#!/usr/bin/expect -f set timeout 10 # 切换到root用户, 然后执行ls和df命令: spawn su - root expect "Password*" send "123456\r" expect "]*" # 通配符 send "ls\r" expect "#*" # 通配符的另一种形式 send "df -Th\r" send "exit\r" # 退出spawn开启的进程 expect eof # 退出此expect交互程序
4.2 通过shell调用expect执行多条命令
注意首行内容, 这种情况下可通过sh script.sh
、bash script.sh
或./script.sh
, 都可以执行这类脚本:
注:简单插入一下重定向的基本用法:
命令符号格式 作用
命令 < 文件 将指定文件作为命令的输入设备
命令 << 分界符 表示从标准输入设备(键盘)中读入,直到遇到分界符才停止(读入的数据不包括分界符),这里的分界符其实就是自定义的字符串
命令 < 文件 1 > 文件 2 将文件 1 作为命令的输入设备,该命令的执行结果输出到文件 2 中。
例: command <<END document END
command是 Shell 命令,<<END是开始标志,END是结束标志,document是输入的文档(也就是一行一行的字符串)。
这种写法告诉 Shell 把 document 部分作为命令需要处理的数据,直到遇见终止符END为止(终止符END不会被读取)。
注意,终止符END必须独占一行,并且要定顶格写。
分界符(终止符)可以是任意的字符串,由用户自己定义,比如 END、MARKER 等。分界符可以出现在正常的数据流中,只要它不是顶格写的独立的一行,就不会被作为结束标志。
Shell中通常将EOF与 << 结合使用,表示后续的输入作为子命令或子Shell的输入,直到遇到EOF为止,再返回到主调Shell。可以把EOF替换成其他东西,意思是把内容当作标准输入传给程序。
回顾一下< <的用法。当shell看到< <的时候,它就会知道下一个词是一个分界符。在该分界符以后的内容都被当作输入,直到shell又看到该分界符(位于单独的一行)。这个分界符可以是你所定义的任何字符串。
#!/bin/bash ip="172.16.22.131" username="root" password="123456" # 指定执行引擎 /usr/bin/expect <<EOF set time 30 spawn ssh $username@$ip df -Th expect { "*yes/no" { send "yes\r"; exp_continue } "*password:" { send "$password\r" } } expect eof EOF
带有命令参数的shell脚本 :
# 脚本文件 #!/bin/bash ip=$1 user=$2 password=$3 expect <<EOF set timeout 10 spawn ssh $user@$ip expect { "yes/no" { send "yes\n";exp_continue } "password" { send "$password\n" } } expect "]#" { send "useradd hehe\n" } expect "]#" { send "touch /tmp/test.txt\n" } expect "]#" { send "exit\n" } expect eof EOF # 运行命令 #./ssh5.sh 192.168.1.10 root 123456
五、常见问题及解决方法
5.1 spawn not found 的解决
出现这个错误的基本上都是初学者: Linux 执行shell脚本有两种方式:
一种是将脚本作为sh的命令行参数, 如sh remote_login.sh, 或sh /data/remote_login.sh; 一种是将脚本作为具有执行权限的可执行脚本, 如./remote_login.sh, 或/data/remote_login.sh.
5.2 expect: spawn id exp6 not open while executing "expect eof"的解决
一个小例子,用于linux下账户的建立:
filename: account.sh,可以使用./account.sh newaccout来执行;
#!/usr/bin/expect set passwd "mypasswd" set timeout 60 if {$argc != 1} { send "usage ./account.sh \$newaccount\n" exit } set user [lindex $argv [expr $argc-1]] spawn sudo useradd -s /bin/bash -g mygroup -m $user expect { "assword" { send_user "sudo now\n" send "$passwd\n" exp_continue } eof { send_user "eof\n" } } spawn sudo passwd $user expect { "assword" { send "$passwd\n" exp_continue } eof { send_user "eof" } } spawn sudo smbpasswd -a $user expect { "assword" { send "$passwd\n" exp_continue } eof { send_user "eof" } }
注意点:
第3行: 对变量赋值的方法;
第4行: 默认情况下,timeout是10秒;
第6行: 参数的数目可以用$argc得到;
第11行:参数存在$argv当中,比如取第一个参数就是[lindex $argv 0];并且如果需要计算的话必须用expr,如计算2-1,则必须用[expr 2-1];
第13行:用spawn来执行一条shell命令,shell命令根据具体情况可自行调整;有文章说sudo要加-S,经过实际测试,无需加-S亦可;
第15行:一般情况下,如果连续做两个expect,那么实际上是串行执行的,用例子中的结构则是并行执行的,主要是看匹配到了哪一个;在这个例子中,如果你写成串行的话,即
expect "assword" send "$passwd\n" expect eof send_user "eof"
那么第一次将会正确运行,因为第一次sudo时需要密码;但是第二次运行时由于密码已经输过(默认情况下sudo密码再次输入时间为5分钟),则不会提示用户去输入,所以第一个expect将无法匹配到assword,而且必须注意的是如果是spawn命令出现交互式提问的但是expect匹配不上的话,那么程序会按照timeout的设置进行等待;可是如果spawn直接发出了eof也就是本例的情况,那么expect "assword"将不会等待,而直接去执行expect eof。 这时就会报expect: spawn id exp6 not open,因为没有spawn在执行,后面的expect脚本也将会因为这个原因而不再执行;所以对于类似sudo这种命令分支不定的情况,最好是使用并行的方式进行处理;
第17行:仅仅是一个用户提示而已,可以删除;
第18行:向spawn进程发送password;
第19行:使得spawn进程在匹配到一个后再去匹配接下来的交互提示;
第21行:eof是必须去匹配的,在spawn进程结束后会向expect发送eof;如果不去匹配,有时也能运行,比如sleep多少秒后再去spawn下一个命令,但是不要依赖这种行为,很有可能今天还可以,明天就不能用了;
下面这个例子比较特殊,在整个过程中就不能expect eof了:
#!/usr/bin/expect set timeout 30 spawn ssh 10.192.224.224 expect "password:" send "mypassword\n" expect "*$" send "mkdir tmpdir\n" expect "*$"
timeout指expect等待命令至多等30seconds
这个例子实际上是通过ssh去登录远程机器,并且在远程机器上创建一个目录,我们看到在我们输入密码后并没有去expect eof,这是因为ssh这个spawn并没有结束,而且手动操作时ssh实际上也不会自己结束除非你exit;所以你只能expect bash的提示符,当然也可以是机器名等,这样才可以在远程创建一个目录。
注意,请不要用spawn mkdir tmpdir,这样会使得上一个spawn即ssh结束,那么你的tmpdir将在本机建立。
当然实际情况下可能会要你确认ssh key,可以通过并行的expect进行处理,不多赘述。
觉得bash很多情况下已经很强大,所以可能用expect只需要掌握这些就好了,其他的如果用到可以再去google了
参考资料:
Linux重定向(输入输出重定向)详解
Linux expect 介绍和用法
shell脚本之Expect免交互的实现
解决问题expect: spawn id exp6 not open while executing "expect eof"
Shell脚本交互之:自动输入密码
Linux中命令行中EOF的用法
————————————————
原文链接:https://blog.csdn.net/fitaotao/article/details/123576889