跳转至

自动应答工具expect的使用

Expect 是用于自动化交互式应用程序

1. 软件介绍

现代的 Shell 对程序提供了最小限度的控制(程序的开始/停止/关闭等),而把交互的特性留给了用户。这意味着有些程序,你不能非交互的运行,比如说 passwd 命令。 Expect 恰恰填补了其中的一些裂痕,解决了在 Unix 环境中长期存在着的一些问题。

Expect 使用 Tcl 作为语言核心,不管程序是交互和还是非交互的,Expect 都能运用。

2. 工具安装

因为Expect需要Tcl编程语言的支持

1
2
3
4
5
6
7
$ sudo yum install -y gcc
$ sudo yum install -y tcl tclx tcl-devel
$ sudo yum install expect


# ubuntu
$ sudo apt install expect

3. 基础知识

主要介绍常见的 4 个命令的使用方式

编号 命令 作用
1 send send 命令接收一个字符串并将该参数发送到进程中
2 expect expect 通常用来等待进程的反馈再发送对应的交互命令
3 spawn spawn 命令用来启动新的进程
4 interact 允许退出自动化进入人工交互

4. 控制结构

介绍 TCL 语言的控制结构

if else

#!/usr/bin/expect

set timeout 10
set alias_host [lindex $argv 0]
set b1_password ASJZOMxlgM^9
set b2_password a0yDuePSLUGM

if {$argc!=1} {
    echo "请输入想要远程连接的服务器: [b1|b2]"
    exit 1
}

if {$alias_host=="b1"} {
    spawn ssh [[emailprotected]](/cdn-cgi/l/email-protection) -p 22
    expect "*password*" {send "$b1_password\r"}
    interact
} elseif {$alias_host=="b2"} {
    spawn ssh [[emailprotected]](/cdn-cgi/l/email-protection) -p 22
    expect "*password*" {send "$b2_password\r"}
    interact
} else {
    send "请输入想要远程连接的服务器: [b1|b2]"
}

switch

#!/usr/bin/expect

set timeout 10
set alias_host [lindex $argv 0]
set b1_password ASJZOMxlgM^9
set b2_password a0yDuePSLUGM

switch -glob -- $file1 {
    b1 {
        spawn ssh [[emailprotected]](/cdn-cgi/l/email-protection) -p 22
        expect "*password*" {send "$b1_password\r"}
        interact
    }
    b2 {
        spawn ssh [[emailprotected]](/cdn-cgi/l/email-protection) -p 22
        expect "*password*" {send "$b2_password\r"}
        interact
}

while

#!/usr/bin/expect

set test 0
while {$test<10} {
    set test [expr {$test + 1}]
    if {$test > 7}
        break
    if "$test < 3"
        continue
}

catch

1
2
3
4
5
6
7
8
#!/usr/bin/expect

proc Error {} {
    error "This is a error for test"
}

catch Error test
puts $test

5. 简单使用

系统指定修改用户密码

1
2
3
4
5
6
7
8
9
#!/usr/bin/expect -d

set timeout 30

spawn passwd [lindex $argv 1]
set password [lindex $argv 2]
expect "*New password:*" {send "$password\r"}
expect "*Retype new password:*" {send "$password\r"}
expect eof

登陆远程服务器并停留在远程服务器上

#!/usr/bin/expect

set timeout 30  # 设置超时时间
set host "100.200.200.200"
set username "root"
set password "123456"

# 给ssh运行进程加个壳用来传递交互指令
spawn ssh $username@$host
# 判断上次输出结果里是否包含指定的字符串
expect {
    # exp_continue表示继续执行下一步
    "*yes/no" {send "yes\r";exp_continue}
    # 匹配即可发送密码到对应进程中
    "*password*" {send "$password\r"}
}
# 执行完成后保持交互状态
interact

传输参数执行登并停留在远程服务器上

# login.exp
#!/usr/tcl/bin/expect

# $argc表示参数个数
if {$argc < 3} {
    puts "Usage:cmd <host>:<username> -p <port>"
    exit 1
}

# 获取第几个参数的内容
set timeout 30
set host [lindex $argv 0]
set username [lindex $argv 1]
set password [lindex $argv 2]
set port [lindex $argv 3]

spawn ssh $username@$host -p $port
expect "*password*" {send "$password\r"}
interact

在 shell 脚本中使用 expect

1
2
3
4
5
6
7
8
# [1] 直接添加expect脚本文件

#!/bin/bash
read -p "please input you user:" -t30 remote_user
read -p "please input you ip:" -t30 remote_ip
read -p "please input you port:" -t30 remote_port
echo "ssh $remote_user:$remote_ip -p $remote_port"
./login.exp $remote_user $remote_ip $remote_port
# [2] 在shell脚本直接写入expect命令

#!/bin/bash
read -p "please input you user:" -t30 remote_user
read -p "please input you ip:" -t30 remote_ip
read -p "please input you port:" -t30 remote_port
echo "ssh $remote_user:$remote_ip -p $remote_port"

expect -d <<EOF
spawn ssh $remote_ip
expect {
"*yes/no" {send "yes\r";exp_continue}
"*password:" {send "Xuansiwei123!\r"}
}
exit
expect eof;
EOF

6. 高级示例

弄懂下面的高级玩法,就可以应对日常的工作使用了

自动 telnet 会话

#!/usr/bin/expect -f

set ip [lindex $argv 0 ]         # 接收第1个参数,作为IP
set userid [lindex $argv 1 ]     # 接收第2个参数,作为userid
set mypassword [lindex $argv 2 ] # 接收第3个参数,作为密码
set mycommand [lindex $argv 3 ]  # 接收第4个参数,作为命令
set timeout 10                   # 设置超时时间

# 向远程服务器请求打开一个telnet会话,并等待服务器询问用户名
spawn telnet $ip
    expect "username:"
    # 输入用户名,并等待服务器询问密码
    send "$userid\r"
    expect "password:"
    # 输入密码,并等待键入需要运行的命令
    send "$mypassword\r"
    expect "%"
    # 输入预先定好的密码,等待运行结果
    send "$mycommand\r"
    expect "%"
    # 将运行结果存入到变量中,显示出来或者写到磁盘中
    set results $expect_out(buffer)
    # 退出telnet会话,等待服务器的退出提示EOF
    send "exit\r"
    expect eof

自动建立 FTP 会话

#!/usr/bin/expect -f

set ip [lindex $argv 0 ]         # 接收第1个参数,作为IP
set userid [lindex $argv 1 ]     # 接收第2个参数,作为Userid
set mypassword [lindex $argv 2 ] # 接收第3个参数,作为密码
set timeout 10                   # 设置超时时间

# 向远程服务器请求打开一个FTP会话,并等待服务器询问用户名
spawn ftp $ip
    expect "username:"
    # 输入用户名,并等待服务器询问密码
    send "$userid\r"
    expect "password:"
    # 输入密码,并等待FTP提示符的出现
    send "$mypassword\r"
    expect "ftp>"
    # 切换到二进制模式,并等待FTP提示符的出现
    send "bin\r"
    expect "ftp>"
    # 关闭ftp的提示符
    send "prompt\r"
    expect "ftp>"
    # 下载所有文件
    send "mget *\r"
    expect "ftp>"
    # 退出此次ftp会话,并等待服务器的退出提示EOF
    send "bye\r"
    expect eof

自动登录 ssh

#!/usr/bin/expect -f

set ip [lindex $argv 0 ]         # 接收第1个参数,作为IP
set username [lindex $argv 1 ]   # 接收第2个参数,作为username
set mypassword [lindex $argv 2 ] # 接收第3个参数,作为密码
set timeout 10                   # 设置超时时间

spawn ssh $username@$ip       # 发送ssh请求
expect {                      # 返回信息匹配
    "*yes/no" { send "yes\r"; exp_continue}  # 第一次ssh连接会提示yes/no,继续
    "*password:" { send "$mypassword\r" }    # 出现密码提示,发送密码
}
interact        # 交互模式,用户会停留在远程服务器上面

自动登录 ssh 执行命令

#!/usr/bin/expect

set IP     [lindex $argv 0]
set USER   [lindex $argv 1]
set PASSWD [lindex $argv 2]
set CMD    [lindex $argv 3]

spawn ssh $USER@$IP $CMD
expect {
    "(yes/no)?" {
        send "yes\r"
        expect "password:"
        send "$PASSWD\r"
        }
    "password:" {send "$PASSWD\r"}
    "* to host" {exit 1}
    }
expect eof

批量登录 ssh 服务器执行操作范例 => for 循环

#!/usr/bin/expect

for {set i 10} {$i <= 12} {incr i} {
    set timeout 30
    set ssh_user [lindex $argv 0]
    spawn ssh -i .ssh/$ssh_user abc$i.com

    expect_before "no)?" {
    send "yes\r" }
    sleep 1
    expect "password*"
    send "hello\r"
    expect "*#"
    send "echo hello expect! > /tmp/expect.txt\r"
    expect "*#"
    send "echo\r"
}

exit

批量登录 ssh 并执行命令 => foreach 语法

#!/usr/bin/expect

if {$argc!=2} {
    send_user "usage: ./expect ssh_user password\n"
    exit
}

foreach i {11 12} {
    set timeout 30
    set ssh_user [lindex $argv 0]
    set password [lindex $argv 1]
    spawn ssh -i .ssh/$ssh_user [[emailprotected]](/cdn-cgi/l/email-protection)
    expect_before "no)?" {
    send "yes\r" }
    sleep 1

    expect "Enter passphrase for key*"
    send "password\r"
    expect "*#"
    send "echo hello expect! > /tmp/expect.txt\r"
    expect "*#"
    send "echo\r"
}

exit

批量 ssh 执行命令 => 用 shell 调用 tclsh 方式、多进程同时执行

#!/bin/sh

exec tclsh $0 "$@"
package require Expect
set username [lindex $argv 0]
set password [lindex $argv 1]
set argv [lrange $argv 2 end]
set prompt "(%|#|\\$) $"

foreach ip $argv {
    spawn ssh -t $username@$ip sh
    lappend ids $spawn_id
}

expect_before -i ids eof {
    set index [lsearch $ids $expect_out(spawn_id)]
    set ids [lreplace $ids $index $index]
    if [llength $ids] exp_continue
}
expect -i ids "(yes/no)\\?" {
    send -i $expect_out(spawn_id) yes\r
    exp_continue
} -i ids "Enter passphrase for key" {
    send -i $expect_out(spawn_id) \r
    exp_continue
} -i ids "assword:" {
    send -i $expect_out(spawn_id) $password\r
    exp_continue
} -i ids -re $prompt {
    set spawn_id $expect_out(spawn_id)
    send "echo hello; exit\r"
    exp_continue
} timeout {
    exit 1
}

使用 ssh 自动登录 expect 脚本 => ssh.expect

The authenticity of host '192.168.xxx.xxx (192.168.xxx.xxx)' can't be established.
RSA key fingerprint is 25:e8:4c:89:a3:b2:06:ee:de:66:c7:7e:1b:fa:1c:c5.
Are you sure you want to continue connecting (yes/no)?

Warning: Permanently added '192.168.xxx.xxx' (RSA) to the list of known hosts.
Enter passphrase for key '/data/key/my_dsa':

Last login: Sun Jan 26 13:39:37 2019 from 192.168.xxx.xxx
[root@master003 ~]#
[[emailprotected]](/cdn-cgi/l/email-protection)'s password:

Last login: Thu Jan 23 17:50:43 2019 from 192.168.xxx.xxx
#!/usr/bin/expect -f

if {$argc < 4} {
    send_user "Usage:\n  $argv0 IPaddr User Passwd Port Passphrase\n"
    puts stderr "argv error!\n"
    sleep 1
    exit 1
}

set timeout 30
set ip         [lindex $argv 0 ]
set user       [lindex $argv 1 ]
set passwd     [lindex $argv 2 ]
set port       [lindex $argv 3 ]
set passphrase [lindex $argv 4 ]

if {$port == ""} {
    set port 22
}

spawn ssh $user@$ip -p $port

expect_before "(yes/no)\\?" {
    send "yes\r"}

expect \
"Enter passphrase for key*" {
    send "$passphrase\r"
    exp_continue
} " password:?" {
    send "$passwd\r"
    exp_continue
} "*\[#\\\$]" {
    interact
} "* to host" {
    send_user "Connect faild!"
    exit 2
} timeout {
    send_user "Connect timeout!"
    exit 2
} eof {
    send_user "Lost connect!"
    exit
}

7. 参考博客

expect - 教程中文版
expect - 正则模式说明
expect - 自动交互脚本