前言

最近研究了一下SSH远程免密登录,也遇到了一些问题,比如远程登录成功后执行脚本的时候,如何自动输入密码,如何进行其他的需要手动输入信息的交互式操作,有两种解决方案,如

echo 'xxx'| sudo script

这样的管道式操作,还有

!# /usr/bin/bash
sudo xxx <<EOF
xxx
xxx
EOF

这类的重定向方法,但是对于类似su user这样的操作会报错

standard in must be a tty

stdin(标准输入设备)必须是一个tty(虚拟终端,就是人机交互的接口),最终被我找到了一个很好的解决方案,就是使用expect来实现交互式操作。Expect是一个免费的编程工具语言,用来实现自动和交互式任务进行通信,而无需人的干预。

安装

# CentOS安装
yum install expect
# Ubuntu安装
sudo apt-get install expect

安装成功后如下图所示:
安装expect成功

参数说明

  • set:可以设置超时,也可以设置变量,然后在脚本中可以使用$变量或者${变量}使用
  • timeoutexpect超时等待时间,默认10S
  • spawn:执行一个Shell命令
  • expect "":对通过spawn执行的shell脚本的返回进行判断,是否包含""中的字段
  • exp_continue:继续执行下面匹配
  • \r:可以理解为回车
  • $argc:统计位置参数数量
  • [lindex $argv 0]:脚本后第一个参数,类似于shell中$1,以此类推
  • puts:打印字符串,类似于echo
  • awk -v I="$ip":赋值变量
  • expect{...}:输入多行记录
  • timeout -1:永不超时退出
  • expect eof :等待执行结束,若没有这一句,可能导致命令还没执行,脚本就结束了
  • log_file /var/log/expect.log:记录交互信息,一般crontab时使用
  • interact:退出expect返回终端,可以继续输入,否则将一直在expect不能退出到终端
  • if{} {}elif{}{}else{}(注意if与花括号,花括号和花括号之间都需要有空格,else不能单独放一行,所以else要跟在}后面)

经过我实际测试,一个expect脚本只能有一个spawn执行命令,如果还要执行其他的shell命令的话,直接使用send就可执行shell命令

简单实例

  1. 切换用户创建测试文件夹
#!/usr/bin/expect
set timeout 30
set passwd "xxxxxxx"
spawn su root
expect "Password:"
send "$passwd\r"
send "cd ~\r"
send "mkdir test\r"
interact
expect eof
exit
  1. 实现远程登录服务器,并切换到root用户下执行关闭防火墙的命令,然后退出
#!/usr/bin/expect
if {$argc < 4} {
    #do something
    send_user "usage: $argv0 <remote_user> <remote_host> <remote_pwd> <remote_root_pwd>"
    exit
}
set timeout -1
set remote_user [lindex $argv 0] # 远程服务器用户名
set remote_host [lindex $argv 1] # 远程服务器域名
set remote_pwd [lindex $argv 2] # 远程服务器密码
set remote_root_pwd [lindex $argv 3] # 远程服务器根用户密码
# 远程登录
spawn ssh ${remote_user}@${remote_host}
expect "*assword:" {send "${remote_pwd}\r"}
expect "Last login:"
# 切换到 root
send "su\r"
expect "*assword:" {send "${remote_root_pwd}\r"}
# 执行关闭防火墙命令
send "service iptables stop\r"
send "exit\r"
send "exit\r"
expect eof

参考资料