脚本概述
当需要在很多(比如几十至几百)台机器上编译同一程序时,如果一个个地手工拷贝源码、再编译,那么效率就很低,为了能大量节省手工、并行地编译,因此写了一个脚本,该脚本基于自动化脚本语言expect(expect基于tcl)实现,基本原理是针对每个远程主机,创建一个子进程,在该子进程内先调用scp拷贝源码到远程主机,再用ssh登录到远程主机、发送cd、configure和make命令,交互期间的命令输出多用正则分析,最终的编译输出保存到当前目录output子目录下。其命令行参数说明如下:
● 第1参数为远程主机配置文件:一个多行文本文件,每行格式为IP 用户名 密码,空格符分隔,支持#注释。
● 第2参数为本地主机源码目录:要求该目录存在Makefile和configure文件。
● 第3参数为远程主机目标目录:用于存放源码的位置。
脚本实现
拷贝源码
当需要在很多(比如几十至几百)台机器上编译同一程序时,如果一个个地手工拷贝源码、再编译,那么效率就很低,为了能大量节省手工、并行地编译,因此写了一个脚本,该脚本基于自动化脚本语言expect(expect基于tcl)实现,基本原理是针对每个远程主机,创建一个子进程,在该子进程内先调用scp拷贝源码到远程主机,再用ssh登录到远程主机、发送cd、configure和make命令,交互期间的命令输出多用正则分析,最终的编译输出保存到当前目录output子目录下。其命令行参数说明如下:
● 第1参数为远程主机配置文件:一个多行文本文件,每行格式为IP 用户名 密码,空格符分隔,支持#注释。
● 第2参数为本地主机源码目录:要求该目录存在Makefile和configure文件。
● 第3参数为远程主机目标目录:用于存放源码的位置。
脚本实现
拷贝源码
1
proc copy_file {host user srcdir dstdir passwd {to 10} } {
2
if [catch "spawn scp -rq $srcdir $user@$host:$dstdir" msg] {
3
send_error "failed to spawn scp: $msg\n"
4
exit 1
5
}
6
7
set timeout $to
8
expect_after eof {
9
send_error "$host scp died unexpectedly\n"
10
exit 1
11
}
12
expect {
13
"(yes/no)?" { send "yes\r"; exp_continue }
14
-re "(?:P|p)assword:" { send "$passwd\r" }
15
timeout { do_timeout "$host scp" }
16
}
17![]()
18
expect {
19
full_buffer { exp_continue }
20
timeout { exp_continue }
21
eof
22
}
23
}

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

第2行调用spawn命令执行scp命令,并用catch捕捉错误;当执行成功后,第12行用expect等待远端输出(超时默认为10秒),第13、14行自动输入用户名和密码,当过程中网络连接断开时,会匹配到第8行的eof;当输出完成连接关闭时,会匹配到第21行的eof;如果输出太多超过expect内部的buffer时,会匹配到第19行的full_buffer,这里由于为了提高效率,使用了静默方式的scp,因些实际会匹配到第20行的timeout,不管匹配到哪种情况,都要继续直到eof。
执行编译
执行编译
1
proc do_make {host user passwd subdir {to 10} } {
2
if [catch {spawn ssh $user@$host} msg ] {
3
send_error "failed to spawn ssh: $msg\n"
4
exit 1
5
}
6
7
set timeout $to
8
expect_after eof {
9
send_error "$host ssh died unexpectedly\n"
10
exit 1
11
}
12
13
expect {
14
"*yes/no" { send "yes\r"; exp_continue }
15
-re "(?:P|p)assword:" { send "$passwd\r" }
16
timeout { do_timeout "$host ssh" }
17
}
18
wait_cmd $spawn_id passwd
19![]()
20
send "cd $subdir\r"
21
wait_cmd $spawn_id cd
22
23
send "source configure\r"
24
wait_cmd $spawn_id configure
25![]()
26
send "make\r"
27
wait_cmd $spawn_id make
28![]()
29
send "exit\r"
30
expect eof
31
}

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

关于spawn和expect的解释与上节拷贝源码相同,不同的是依次发送命令cd、source configure、make,每个命令须等到命令提示符后(调用自定义函数wait_cmd)再发下一个,最后发送exit退出ssh、导致连接关闭,匹配到最后一行的eof。对于有的项目源码,可能没有或不用配置,那么configure文件可以不存在或内容为空,如果不存在导致报错也没关系,不影响make;如果configure出错,那么make也会出错。这里使用source是为了使配置在当前shell中生效。
主循环
主循环
1
set f [open $file r]
2
set curtime [clock seconds]
3![]()
4
log_user 0
5
set s {[:blank:]}
6
set pattern "^(\[^#$s]+)\[$s]+(\[^$s]+)\[$s]+(\[^$s]+)"
7![]()
8
while { [gets $f line] != -1 } {
9
if { ![regexp $pattern [string trimleft $line] ? host user passwd] } {
10
continue
11
}
12
send_user "$host $user $passwd\n"
13
if { ![fork] } {
14
15
set filename output/${host}_[clock format $curtime -format %y.%m.%d_%H.%M.%S].log
16
log_file -noappend -a $filename
17![]()
18
copy_file $host $user $srcdir $dstdir $passwd 30
19
do_make $host $user $passwd $subdir 30
20![]()
21
send_user "$host finish\n"
22
exit
23
}
24
}

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

打开远程主机配置文件,读取每一行直到文件尾,忽略注释行,用正则提取IP、用户名和密码,创建子进程,按IP和当前时间命名log文件(由于前面调用log_user 0关闭了控制台输出,因此为了能记录输出到日志文件,一定要加-a选项),最后调用函数copy_file和do_make。
完整脚本下载:autobuild.zip
完整脚本下载:autobuild.zip