解析文件并为其他程序创建动态数据结构的方法
大家好,
背景:我有一个客户,他们在数据中心有一些基于Python的构建脚本,我是接手的。我并没有参与最初的设计,所以在能改什么、不能改什么上有些受限。我的客户有一个属性文件,他们在数据中心使用这个文件。文件中的一些值是用来构建他们的服务器的,不幸的是,还有其他应用程序也在使用这些值,所以我不能随便改动它们来方便自己。
我想做的是让这些脚本更灵活一些,这样可以分配更多的主机,这样我就不需要不断更新脚本,只需在属性文件中添加更多主机就可以了。不幸的是,我不能更改当前的属性文件,只能在这个基础上工作。
这个属性文件大概长这样:
projectName.ClusterNameServer1.sslport=443
projectName.ClusterNameServer1.port=80
projectName.ClusterNameServer1.host=myHostA
projectName.ClusterNameServer2.sslport=443
projectName.ClusterNameServer2.port=80
projectName.ClusterNameServer2.host=myHostB
在他们的部署脚本中,基本上有很多类似 if projectName.ClusterNameServerX
的语句,其中 X 是一些定义好的数字,然后 do something
,例如:
if projectName.ClusterNameServer1.host != "" do X
if projectName.ClusterNameServer2.host != "" do X
if projectName.ClusterNameServer3.host != "" do X
当他们添加另一个主机(比如 Serve4)时,就又添加了一个 if 语句。
我想做的是让脚本更灵活,解析这个属性文件,把需要的内容放到某种数据结构中,然后传递给部署脚本,再遍历这个结构来进行部署,这样我就不需要不断添加一堆 if 语句来处理每个主机。我很好奇其他人会怎么解析这个文件,他们会使用什么样的数据结构,以及他们会如何根据 ClusterNameServer# 或其他方式来对内容进行分组。
谢谢!
3 个回答
执行以下代码会创建一个字典,这个字典的键是服务器的编号,值是(ssl端口,端口,主机名)
import re
RE = ('projectName.ClusterNameServer(\d+)\.sslport=(\d+)\s+'
'projectName.ClusterNameServer\\1\.port=(\d+)\s+'
'projectName.ClusterNameServer\\1\.host=(\S+)(?:\s+|\Z)')
propertyfile_name = 'servers.txt'
with open(propertyfile_name) as f:
dico = dict( (server,(s,p,h)) for (server,s,p,h) in re.findall(RE,f.read()) )
print dico
结果
dico== {'1': ('443', '80', 'myHostA'), '2': ('443', '80', 'myHostB')}
然后这些字典数据可以“传递给部署脚本”;我不太清楚你们有什么方法可以传递这些数据。可以用一个普通文件,或者更好的是,用一个pickle文件。
最简单的办法就是把上面代码的内容直接放到“部署脚本”里,这样它们就能直接使用这些数据,而不需要从外部获取。
.
编辑
还有一种我觉得更好的解决方案:
import csv
from itertools import ifilter
propertyfile_name = 'servers.txt'
with open(propertyfile_name,'rb') as f:
dico = dict( ifilter(None,csv.reader(f,delimiter='=')) )
print dico
ifilter()
函数是必要的,因为文件中可能有空行。
结果
{'projectName.ClusterNameServer2.host': 'myHostB', 'projectName.ClusterNameServer2.port': '80', 'projectName.ClusterNameServer1.host': 'myHostA', 'projectName.ClusterNameServer1.port': '80', 'projectName.ClusterNameServer2.sslport': '443', 'projectName.ClusterNameServer1.sslport': '443'}
.
编辑
RE = ('(projectName.ClusterNameServer\d+\.(?:sslport|port|host))=(\S+)(?:\s+|\Z)')
propertyfile_name = 'servers.txt'
with open(propertyfile_name) as f:
dico = dict( re.findall(RE,f.read()) )
print dico
结果是一样的
如果你想要结果
dico== {'1': ('443', '80', 'myHostA'), '2': ('443', '80', 'myHostB')}
我明天会做
因为你的属性名称有一些内部结构,你可以试试用pyparsing来处理你的属性文件:
from pyparsing import Word, alphas, alphanums, delimitedList, restOfLine
props = """
projectName.ClusterNameServer1.sslport=443
projectName.ClusterNameServer1.port=80
projectName.ClusterNameServer1.host=myHostA
projectName.ClusterNameServer2.sslport=443
projectName.ClusterNameServer2.port=80
projectName.ClusterNameServer2.host=myHostB
"""
# define format of a single property definition line
ident = Word(alphas, alphanums+'_')
propertyName = delimitedList(ident,'.')
propertyDefn = propertyName("name") + '=' + restOfLine("value")
# sample code that parses the properties and accesses the
# name and value fields
for prop in propertyDefn.searchString(props):
print '.'.join(prop.name), '->', prop.value
输出结果:
projectName.ClusterNameServer1.sslport -> 443
projectName.ClusterNameServer1.port -> 80
projectName.ClusterNameServer1.host -> myHostA
projectName.ClusterNameServer2.sslport -> 443
projectName.ClusterNameServer2.port -> 80
projectName.ClusterNameServer2.host -> myHostB
如果你可以依赖于项目-服务器-参数这种结构,你可以使用defaultdict(来自collections模块)来构建一个层次化的对象:
# use defaultdicts to accumulate these values into a nested structure
from collections import defaultdict
properties = defaultdict(lambda:defaultdict(dict))
# build hierarchical property structure
# (assumes very rigid project-server-parameter naming)
for prop in propertyDefn.searchString(props):
properties[prop.name[0]][prop.name[1]][prop.name[2]] = prop.value
# show data in nice outline form
for project in properties:
print '-', project
for cluster in properties[project]:
print ' -', cluster
for param in properties[project][cluster]:
print ' -', param, properties[project][cluster][param]
输出结果:
- projectName
- ClusterNameServer1
- host myHostA
- sslport 443
- port 80
- ClusterNameServer2
- host myHostB
- sslport 443
- port 80
创建一个sqlite数据库,用来存储属性文件里的数据。
写一个脚本,把属性文件里的数据导入到sqlite数据库中。
写一个脚本,可以根据sqlite数据库里的数据重新生成属性文件。现在可以通过在sqlite数据库中插入新行来添加新的主机。每次更新或插入数据时,运行这个脚本,以确保旧的脚本能正常工作,直到第五步完成。
让所有新的脚本都和sqlite数据库互动,而不是直接使用属性文件。
重写旧的脚本,让它们使用sqlite数据库。例如,使用sql查询的结果进行循环,而不是添加更多的
if
语句。