使用expect+python拉取数据并生成报表


虽然脚本周一的时候就写好了,但是昨天才是第一次正式使用。加上昨天写了第二篇shell的tips,所以expect+python的脚本gen_report今天才写。

首先说一下背景。由于工作需要,有时我需要ssh到服务器上拉取运行数据生成报表。整个过程很枯燥,就是登录服务器,输入密码,运行二三十条很像的命令,把数据复制到microsoft excel或者libre office calc中计算。一言以蔽之:整个人肉操作。

很明显,如果让我天天干,我肯定不愿意。但是即使不是天天干,一周干一次,我也不愿意,因为命令执行很慢,来回弄肯定要半天。按照《时间管理 给系统管理员》,这种时候最适合写脚本了。

常规策略是在服务器上写好一个脚本,ssh上去执行一下,再使用scp把结果下载下来,最后使用比如python来解析生成csv等报表。但是,不行。首先遇到的问题就是服务器貌似不支持publickey,其次服务器上命令有限制(很常见,你不是管理员),不支持publickey的直接后果是ssh后执行命令和scp无法使用,只能另外寻找方法。

由于某些原因,我知道expect。虽然交互式的命令很好,但是如果你要让交互式的命令自动化的话,却很麻烦,比如ssh,ftp。这个时候expect可以帮助你解决问题。证明是网上搜索expect ssh login一抓一大把。所以我现在的考虑是通过expect代替ssh登录服务器执行命令。因为不能用scp,所以考虑把expect通过重定向或者tee放入某个文件中,最后让python解析这个日志文件即可。(最近重新看了点expect的书,貌似expect可以直接设置日志文件,不用tee之类的重定向,之后有空可以尝试下。)

基本策略确定了,方向基本也清楚了。由于涉及expect和python两种语言和程序,所以我选择shell做总控制的脚本。以下是总控制脚本即gen_report的内容(部分和工作相关的内容已经做了修改):

#!/bin/sh

FETCH_LOG=./fetch_log
PARSE_LOG=./parse_log
TODAY=`date +%Y-%m-%d`
LOG_FILE=foo-$TODAY.log
CSV_FILE=foo-$TODAY.csv

# $1 is password
$FETCH_LOG $1 2>&1 | tee $LOG_FILE
$PARSE_LOG $LOG_FILE | tee $CSV_FILE
rm $LOG_FILE

解释一下,fetch_log是expect脚本,parse_log是python脚本,中间文件是LOG_FILE,最后生成的是CSV_FILE。
脚本需要输入的是$1,即注释中的密码。考虑到有人不知道tee这个命令,解释一下tee是一个把内容输出到文件和标准输出的命令。其次2>&1含义是把错误输出到标准输出中,否则你的日志文件中可能看不到错误信息。
脚本逻辑很简单,如果之前的解释看懂了的话,三行脚本的内容很容易理解。就是expect获取输出,python解析,最后删除中间文件。

fetch_log即expect脚本由于和工作相关的内容比较多,这里只列出部分:

#!/usr/bin/expect -f

set timeout -1
set prompt "*$ "

proc stat {a b c d} {
  // omit
}

set password [lindex $argv 0]

spawn ssh user@server
expect "*Password:*" {
  send "$password\n"
}

stat "1" "2" "3" "4"
stat "2" "2" "3" "4"
stat "3" "2" "3" "4"
stat "4" "2" "3" "4"

expect $prompt {
  exit
}

解释一下,spawn->expect password->enter password是典型的登录服务器的方式。之后的stat是我需要在远程服务器上执行的命令,最后执行完之后exit。需要注意的是expect默认超时是10s,由于服务器命令执行比较耗时,所以我调整为-1。更多的expect资料建议man expect,google expect或者看《exploring expect》。

最后是解析的python脚本parse_log。这个脚本预期的输入格式是这样的:

$prompt $key
command
$result

第一行为提示符+命令关键字,第二行为具体命令,第三行为执行结果(单行)。python解析脚本如下:

#!/usr/bin/env python

import sys

def row(key, count):
	return key.split(',') + [count]

def parse(filepath):
	f = open(filepath)
	rows = []
	while True:
		line = f.readline()
		if not line:
			break
		if line.startswith('prompt:'):
			# pass command
			f.readline()
			count = f.readline()
                        # cut prompt from position 6
			rows.append(row(line.rstrip()[6:], count.rstrip()))
	f.close()
	return rows

if __name__ == "__main__":
	print 'header'
	for row in parse(sys.argv[1]):
		print ','.join(row)

至此,所有脚本介绍完毕。昨天执行的时候运行良好,大概只花了半个多小时,不用自己去关注。结果大致是个csv的表格,这里就不具体列出来了。
最后,感叹了linux果然是开发工程师必学的工具啊,对平时的任务非常有用,相比之下,我还真不知道windows该怎么弄……


One response to “使用expect+python拉取数据并生成报表”

  1. […] 上面的代码通过参数输入password完成了不能直接自动化的部分,剩下的让expect帮忙输入即可。 如果说只是让expect输入几条命令,貌似没啥。实际上,我用expect是来拉取运行数据的,一次大概要输入42条命令。具体参见这里和这里。使用后拉取报告时间从2h急剧缩减到3min。吐槽一句,通过ssh拉取运行数据太原始了…… […]