colaghost

自己的世界。。。

用expect来实现自动交互的shell脚本

有时候可能实现自动交互过程,如自动登陆到远程主机等,由于需要输入用户名密码神马的,得自己呆在那里等它登陆完成,比较麻烦,单纯用shell脚本又没法完成,因为像ssh连接远程主机是不从stdin读入数据的。这时候就需要expect出马了。

Expect是基于TCL的,作为一个脚本语言,expect能在无需管理员参与的情况下实现自动交互(比如passwd,fsck,telnet等),expect也能用于自动测试一些应用程序。

搜索一下其实网上很多使用expect来完成密码登陆的例子的:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
auto_login_ssh () {
    expect -c "set timeout -1;
                spawn -noecho ssh -o StrictHostKeyChecking=no $2 ${@:3};
                expect *assword:*;
                send -- $1\r;
                interact;";
}
 
auto_login_ssh passwd user@

但实际上这个脚本不能做到完全自动化,一旦密码错误或者远程主机未响应神马的,expect还是正常退出,并不能判断到底哪一步出问题了。由于使用expect后,程序的exit status是expect的,所以这时候对expect做处理。当expect遇不到期望的输出值时,就会执行eof分支,这时候我们只要在eof分支加上退出值即可以判定到底是在哪一步出了问题。

这里用两个简单的脚本说明,这样比较容易弄清楚。

called.sh

1
2
#!/bin/bash
echo "a"

可以看出上述脚本是输出“a”,也就是期望输出值应该为”a”。下面看用expect实现的交互脚本:
caller.sh

1
2
3
4
5
6
7
8
9
#!/bin/sh
expect -c "set timeout -1;
           spawn called.sh;
           expect{
               \"b\"{exit 1;}
               eof{exit 2;}
           }
           "
echo $?

实际上called.sh的输出值是”a”,但caller.sh的期望值为”b”,这时候实际上是跳到eof,这时候按状态2退出,也就是说$?实际上为2.这时候就可以判断交互脚本具体的执行情况。

这里只是一个简单的实例而已,并没有任何实际的意义,但是它说明了如何处理一些异常的情况,这样只要将上面的自动完成ssh连接远程主机的脚本加上异常情况处理即可变成一个真正完全自动交互的脚本了,以下是做修改后的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash
auto_smart_ssh () {
    expect -c "set timeout -1;
                spawn ssh -o StrictHostKeyChecking=no $2 ${@:3};
                expect {
                    *assword:* {send -- $1\r;
                                 expect {
                                    *denied* {exit 2;}
                                    eof
                                 }
                    }
                    eof         {exit 1;}
                }
                "
    return $?
}
 
auto_smart_ssh passwd user@host ls /var
echo -e "\n---Exit Status: $?"

注:以上自动完成ssh连接远程主机例子来自于apt-blog.net