背景
当版本从svn上更新下来后,需要修改多个配置文件中的多个地方,如果忘记修改或者修改错误了以后,则会出现很多不可控的风险,造成使用者对当前系统情况判断错误,浪费时间、精力。教导新员工时也无法确保全部配置文件的全部修改地方都教到。为了使使用者使用方便,为了使不会因配置文件忘记修改及修改错误导致的时间、精力浪费,故编写自动修改配置文件脚本,并编写此使用手册。
功能
修改AutoChangeConfig.xml这个配置文件中所记录的需要修改的配置文件,将里面的指定内容修改为所设置的内容。
将备份的配置文件进行还原。
使用方法
修改
将AutoChangeConfig与AutoChangeConfig.xml两个文件放置在服务器任意目录下,修改AutoChangeConfig.xml文件,执行AutoChangeConfig后即可完成修改。
还原
运行AutoChangeConfig时,后面跟着参数,参数内容为备份时的时间点,如要还原BackUp文件夹下20180607183011这个文件夹下所备份的内容,那么则需要执行命令AutoChangeConfig 20180607183011即可。
AutoChangeConfig.xml的修改
结构
AutoChangeConfig.xml文件由4种节点构成,分别为<config>、<modify>、<file>、<change>构成。
<config>:配置文件的顶点,无实际意义。
<modify>:文件标志节点,每对节点对应一个要修改的文件。
<file>:文件路径节点,节点内容为要修改的文件绝对路径。
<change>:修改内容节点,不同类型的配置文件,该节点里的内容编写不一样。
xml格式
对内容进行修改
针对xml格式,特点是节点可成对出现或者不成对出现、同名节点可出现若干、不同节点名同内容的节点可出现若干、可能会有若干注释行出现,所以,如果要修改这种文件,AutoChangeConfig.xml要做如下修改:
1. 先创建一对<modify>节点。
2. 创建一对<file>子节点,该节点的内容为要修改的xml格式文件的绝对路径,路径要包含该文件的名称及后缀。
3. 根据需要创建一对或者多对<change>子节点,子节点的内容为要修改的内容块,并且用<![CDATA[]]>包裹。该内容块必须为有实际意义、实际特征的,且能与其他区域有明显分隔的。如:
|
对内容进行增加
xml格式的配置文件,不支持对内容的增加。
ini格式
对内容进行修改
ini格式文件的特点是内容是以节、键、值出现、节有可能重复、一个节下面可以出现相同的键和值,虽然这样非法、不同的节下面可以出现相同的键和值、可能会有若干注释行出现。对于这种格式的配置文件修改,要区分要不要指定修改某个节下面内容。所以,如果要修改这种文件,AutoChangeConfig.xml要做如下修改:
1. 先创建一对<modify>节点。
2. 创建一对<file>子节点,该节点的内容为要修改的ini格式文件的绝对路径,路径要包含该文件的名称及后缀。
3. 如果要针对某个节下面的内容进行修改的话,则需要创建<nodename>节点,里面为要指定的节的节点名。
4. 根据需要创建一对或者多对<change>子节点,每对节点中写入要修改的内容的那一行,内容顶行最前面写,即使前面是空字符。如果要修改多行,则需要创建多个<change>节点。
|
对内容进行增加
若要在原文添加没有的内容,那么仅需将新加的行作为一个change节点对写入AutoChangeConfig.xml中,程序即会判断,若没有相似的,则就会增加该行.
properties格式
对内容进行修改
Properties格式文件的特点是内容均以键值对的形式出现、不同key相同value的情况会有、不排除相同key不同value以及相同key相同value的情况出现、可能会有若干注释行出现。对于这种格式的配置文件修改,要对AutoChangeConfig.xml做如下修改:
1. 先创建一对<modify>节点。
2. 创建一对<file>子节点,该节点的内容为要修改的Properties格式文件的绝对路径,路径要包含该文件的名称及后缀。
3. 根据需要创建一对或者多对<change>子节点,每对节点中写入要修改的内容的那一行,内容顶行最前面写,即使前面是空字符。如果要修改多行,则需要创建多个<change>节点。
原文:
|
AutoChangeConfig.xml写为如下内容:
|
对内容进行增加
|
原文变为:
|
cfg格式
对内容进行修改
cfg格式文件的特点是内容无固定格式、会存在若干注释行,注释也有可能会有多种格式,目前约定以#作为注释的标志。针对这种格式,要对AutoChangeConfig.xml做如下修改:
1.先创建一对<modify>节点。
2.创建一对<file>子节点,该节点的内容为要修改的cfg格式文件的绝对路径,路径要包含该文件的名称及后缀。
3.根据需要创建一对或者多对<change>子节点,每对节点中写入要修改的内容的那一行,内容顶行最前面写,即使前面是空字符。如果要修改多行,则需要创建多个<change>节点。
原文为:
|
AutoChangeConfig.xml修改为:
|
对内容进行增加
若要增加原文没有的内容,那么仅需将新加的行作为一个change节点对写入AutoChangeConfig.xml中,程序即会判断,若没有相似的,则就会增加该行,依然拿上方提到的原文为例:
AutoChangeConfig.xml修改为:
|
原文变为:
|
日志
日志文件在脚本同级目录LOG下存放,是类似AutoChangeConfigLOG20180605205100.log的文件名,其中的数字为生成日志的时间。
日志正常情况下是以INFO级别进行打印,当出现异常的时候用ERROR级别进行打印。
日志样例:
2018-05-31-Thu 20:10:30 ChangeConfig 55 INFO Begin Read Config xml
2018-05-31-Thu 20:10:31 ChangeConfig 319 INFO Begin Backup "/tomcat_8083/webapps/xtboss/WEB-INF/classes/applicationContext.xml"
2018-05-31-Thu 20:10:31 ChangeConfig 351 INFO Backup "/tomcat_8083/webapps/xtboss/WEB-INF/classes/applicationContext.xml" done
2018-05-31-Thu 20:10:31 ChangeConfig 186 INFO Begin modify xml file
2018-05-31-Thu 20:10:31 ChangeConfig 187 INFO The modify xml file is "/tomcat_8083/webapps/xtboss/WEB-INF/classes/applicationContext.xml"
2018-05-31-Thu 20:10:31 ChangeConfig 313 INFO Modify xml's first line number is 40
2018-05-31-Thu 20:10:31 ChangeConfig 245 INFO Modify xml file done.
2018-05-31-Thu 20:10:31 ChangeConfig 319 INFO Begin Backup "/tomcat_8083/webapps/xtboss/WEB-INF/classes/alipay.properties"
2018-05-31-Thu 20:10:31 ChangeConfig 351 INFO Backup "/tomcat_8083/webapps/xtboss/WEB-INF/classes/alipay.properties" done
2018-05-31-Thu 20:10:31 ChangeConfig 89 INFO Begin modify properties file
2018-05-31-Thu 20:10:31 ChangeConfig 90 INFO The modify properties file is "/tomcat_8083/webapps/xtboss/WEB-INF/classes/alipay.properties"
2018-05-31-Thu 20:10:31 ChangeConfig 276 INFO Now modify alipay.notify_url=http\
2018-05-31-Thu 20:10:31 ChangeConfig 276 INFO Now modify alipay.return_url=http\
2018-05-31-Thu 20:10:31 ChangeConfig 276 INFO Now modify ln_alipay.notify_url=http\
2018-05-31-Thu 20:10:31 ChangeConfig 276 INFO Now modify ln_alipay.return_url=http\
2018-05-31-Thu 20:10:31 ChangeConfig 276 INFO Now modify did_alipay.notify_url=http\
2018-05-31-Thu 20:10:31 ChangeConfig 276 INFO Now modify did_alipay.return_url=http\
2018-05-31-Thu 20:10:31 ChangeConfig 92 INFO modify properties file "/tomcat_8083/webapps/xtboss/WEB-INF/classes/alipay.properties" done
解析:
2018-05-31-Thu 20:10:30 ChangeConfig 55 INFO Begin Read Config xml
① 日志打印的日期。
② 日志打印的时间。
③ 日志产生的模块。
④ 日志打印的行号。
⑤ 日志打印的级别。
⑥ 日志打印的具体内容。
Begin Read Config xml:表示程序开始从AutoChangeConfig.xml中读取要修改的信息。
Begin Backup ***/Backup *** done:开始将第一个要修改的文件进行备份。
Begin modify xml file:开始修改xml类型的文件。
The modify xml file is ***:再次确认要修改的xml文件。
Modify xml's first line number is 40:表示要开始替换的块在原文中开始的行数为40。
Modify xml file done:修改xml类型文件结束。
Begin modify properties file:开始修改properties类型文件。
The modify properties file is:再次确认要修改的properties文件。
Now modify ***:开始修改的行的内容
modify properties file *** done:修改properties类型文件结束。
源码
github为:https://github.com/hb5cn/AutoChangeConfig
下面为源码:
# !/usr/local/python
# -*- coding: UTF-8 -*-
import os
import shutil
import sys
import time
import logging
import traceback
import ConfigParser
from xml.etree import ElementTree
class AutoChangeConfig(object):
def __init__(self):
self.modifypath = ''
self.xml_notes_list = []
self.other_notes_list = []
self.ini_notes_list = []
self.ini_notes_tmp_list = []
# Changing the local path to the current text path
os.chdir(os.path.split(os.path.realpath(__file__))[0])
self.home = os.getcwd()
# Set log config
logfloder = '%s/LOG' % self.home
self.time_now = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))
if not os.path.exists(logfloder):
os.mkdir(logfloder)
logfile = '%s/AutoChangeConfigLOG%s.log' % \
(logfloder, self.time_now)
logging.basicConfig(filename=logfile, level=logging.DEBUG,
format='%(asctime)s %(name)s %(lineno)d %(levelname)s %(message)s',
datefmt='%Y-%m-%d-%a %H:%M:%S', filemode='wb')
self.loggerchangeconfig = logging.getLogger('ChangeConfig')
formatter = logging.Formatter('%(asctime)s %(name)s %(lineno)d %(levelname)s %(message)s')
logscr = logging.StreamHandler()
logscr.setFormatter(formatter)
self.loggerchangeconfig.addHandler(logscr)
# Read xml
self.xmlroot = ElementTree.parse('AutoChangeConfig.xml')
# Initialization Delimiter
self.delimiter = [':', '=', '》', '>', '-》', '->', '=>', '=》']
# Initialization read ini
self.conf = ConfigParser.ConfigParser()
ConfigParser.optionsxform = str
# Initialization backup floder path
self.backupfloder = os.path.join(self.home, 'BackUp')
def readconfig(self):
# Get path, suffix, and modify content from configuration file
self.loggerchangeconfig.info('Begin Read Config xml')
for modify_node in self.xmlroot.findall('./modify'):
modify_file_path = modify_node.find('file').text
modify_file_suffix = str(os.path.basename(modify_node.find('file').text)).split('.')[-1]
self.backupconfig(modify_file_path)
self.dispatch(modify_file_suffix, modify_file_path, modify_node.findall('change'), modify_node)
# Backup AutoChangeConfig config file
auto_cfg_file = os.path.join(self.home, 'AutoChangeConfig.xml')
self.backupconfig(auto_cfg_file)
def dispatch(self, suffix, path, changed, modify_node):
# Select different functions according to the suffix
try:
if 'xml' == suffix:
self.lookupannotations(path, suffix)
self.modifyxml(path, changed)
elif 'properties' == suffix:
self.lookupannotations(path, suffix)
self.modifyproperties(path, changed)
elif 'ini' == suffix:
self.lookupannotations(path, suffix)
self.ini_notes_tmp_list = self.ini_notes_list
self.modifyini(path, changed, modify_node)
elif 'cfg' == suffix:
self.lookupannotations(path, suffix)
self.modifycfg(path, changed)
else:
self.loggerchangeconfig.error('There is no suffix matching, Please check the configuration file')
except Exception, dispatch_err:
if dispatch_err:
self.loggerchangeconfig.error(traceback.format_exc())
def modifyproperties(self, path, changed):
self.loggerchangeconfig.info('Begin modify properties file')
self.loggerchangeconfig.info('The modify properties file is \"%s\"' % path)
self.modifyotherfile(path, changed)
self.loggerchangeconfig.info('modify properties file \"%s\" done' % path)
def modifyini(self, path, changed, modify_node):
if modify_node.find('nodename') is not None:
n = 0
begin_num_list_tmp = [0]
end_num_list = []
node_str = '[' + str(modify_node.find('nodename').text) + ']'
self.loggerchangeconfig.info('Begin modify ini file')
self.loggerchangeconfig.info('The modify ini file is \"%s\"' % path)
with open(path, 'rb') as f:
cfg_content = f.read()
while True:
n += 1
self.lookupannotations(path, 'ini')
with open(path, 'rb') as fr:
cfg_content2 = fr.read()
if str(cfg_content2)[begin_num_list_tmp[-1]:].find(node_str) >= 0:
begin_num = int(str(cfg_content2)[begin_num_list_tmp[-1]:].find(node_str)) + len(node_str) + \
begin_num_list_tmp[-1]
begin_num_list_tmp.append(begin_num)
if -1 == str(cfg_content2[begin_num:]).find('['):
end_num = len(cfg_content2)
else:
end_num = str(cfg_content2[begin_num:]).find('[') + begin_num - 1
# print 'begin_num is %d' % begin_num
# print 'end_num: %d' % end_first_num
# print '----'
# print begin_num_list
# print '++++'
# print end_num_list
with open(path, 'wb') as fw:
if not 'false' == self.judgerange(begin_num, self.ini_notes_list):
fw.write(cfg_content2.replace(cfg_content2[begin_num:end_num],
'\nautochangeconfig%d\n' % n))
else:
fw.write(cfg_content2)
else:
break
begin_num_list = [0]
while True:
if str(cfg_content)[begin_num_list[-1]:].find(node_str) >= 0:
begin_num = int(str(cfg_content)[begin_num_list[-1]:].
find(node_str)) + len(node_str) + begin_num_list[-1]
begin_num_list.append(begin_num)
if -1 == str(cfg_content[begin_num:]).find('['):
end_num = len(cfg_content)
else:
end_num = str(cfg_content[begin_num:]).find('[') + begin_num
end_num_list.append(end_num)
# print 'begin_num is %d' % begin_num
# print 'end_num: %d' % end_first_num
# print '----'
# print begin_num_list
# print '++++'
# print end_num_list
# print str(cfg_content[begin_num:end_num])
else:
break
del(begin_num_list[0])
for i in range(0, len(begin_num_list)):
if not 'false' == self.judgerange(begin_num_list[i], self.ini_notes_tmp_list):
with open(path, 'rb') as fr:
cfg_content3 = fr.read()
tmpfile_path = os.path.join(self.home, 'temp')
with open(tmpfile_path, 'wb') as fw:
# print str(cfg_content[begin_num_list[i]:end_num_list[i]])
fw.write(str(cfg_content[begin_num_list[i]:end_num_list[i]-1]))
self.lookupannotations(tmpfile_path, 'other')
self.modifyotherfile(tmpfile_path, changed)
with open(tmpfile_path, 'rb') as fr:
fr_str = fr.read()
# print fr_str
with open(path, 'wb') as fw:
fw.write(str(cfg_content3).replace('\nautochangeconfig%d\n' % (i + 1), fr_str))
os.remove(tmpfile_path)
self.loggerchangeconfig.info('modify ini file \"%s\" done' % path)
else:
self.loggerchangeconfig.info('Begin modify ini file')
self.loggerchangeconfig.info('The modify ini file is \"%s\"' % path)
self.modifyotherfile(path, changed)
self.loggerchangeconfig.info('modify ini file \"%s\" done' % path)
def modifycfg(self, path, changed):
self.loggerchangeconfig.info('Begin modify cfg file')
self.loggerchangeconfig.info('The modify cfg file is \"%s\"' % path)
self.modifyotherfile(path, changed)
self.loggerchangeconfig.info('modify cfg file \"%s\" done' % path)
def modifyxml(self, path, changed):
self.loggerchangeconfig.info('Begin modify xml file')
self.loggerchangeconfig.info('The modify xml file is \"%s\"' % path)
for change_str in changed:
# Read the original text by line and get the list of text.
with open(path, 'rb') as fr:
b_list = fr.readlines()
changed_list = []
original_list = []
number_list = []
first_line_num_list = []
previous_line_num = 0
try:
change_str.text = change_str.text.encode('utf-8')
except Exception, xml_err:
if xml_err:
change_str.text = change_str.text
# Remove the tab to modify the content and cut it by line.
a_list = str(change_str.text).replace('\t', '').splitlines()
for a_list_str in a_list:
if '' != a_list_str:
# Remove the empty line
changed_list.append(a_list_str)
for b_list_str in b_list:
if '' != b_list_str:
# Remove the empty line\tab
original_list.append(b_list_str.replace('\t', '').replace('\n', '').lstrip().rstrip())
for l in range(0, len(original_list)):
if str(original_list[l]) == str(changed_list[0]):
# Lists all rows that modify the contents of the first row in the original article.
if not 'false' == self.judgerange(l, self.xml_notes_list):
first_line_num_list.append(l)
changed_list_lines = len(changed_list)
for i in range(0, changed_list_lines):
try:
# List all the lines that modify the content in the original article.
previous_line_num += original_list[previous_line_num:].index(changed_list[i])
if not 'false' == self.judgerange(previous_line_num, self.xml_notes_list):
number_list.append(previous_line_num)
except Exception, xml_err:
if xml_err:
pass
# List the first lines that are really modified in the original text
last_first_line = self.getlinenumber(number_list, first_line_num_list)
# if -1 == last_first_line:
# self.loggerchangeconfig.error('Can\'t find content, please check your configuration')
# return
with open(path, 'rb') as fr:
c_list = fr.readlines()
for m in range(0, len(str(change_str.text).splitlines(True))):
# Modify the corresponding content in the original text
if m == len(str(change_str.text).splitlines(True)) - 1:
str(change_str.text).splitlines(True)[m] += '\n'
c_list[last_first_line+m] = str(change_str.text).splitlines(True)[m]
with open(path, 'wb') as fw:
fw.writelines(c_list)
self.loggerchangeconfig.info('Modify xml file done.')
def modifyotherfile(self, path, changed):
cfg_content_new = ''
with open(path, 'rb') as f:
cfg_content = f.readlines()
for change_str in changed:
if '' != cfg_content_new:
cfg_content = cfg_content_new
# print '--->' + str(cfg_content) + '<---'
cfg_content_new = self.replacestring(change_str.text, cfg_content)
# print '++++' + str(cfg_content_new) + '++++'
with open(path, 'wb') as f2:
for line in cfg_content_new:
f2.write(line)
def replacestring(self, replacestr, originalcontent):
"""
Find in the article whether there is a row that is similar to the string to replace,
and if so, replace the line with that string
:param replacestr: A string to replace
:param originalcontent: A list of the whole articles read by line
:return: A list of articles read by line after the replacement
"""
find_separator = ''
# Cut the string to be replaced by a special character
for symbol in self.delimiter:
find_key = 'false'
try:
if str(replacestr).split(symbol)[1]:
self.loggerchangeconfig.info('Now modify %s' % str(str(replacestr).split(symbol)[0]))
for i in range(0, len(originalcontent)):
# Look for a similar string in each line
if not 'false' == self.judgerange(i, self.other_notes_list):
begin_num = str(originalcontent[i]).find(str(replacestr).split(symbol)[0] + symbol)
if begin_num == 0:
# print '>>>' + str(symbol) + '<<<'
# print '>>>' + str(begin_num) + '<<<'
# print '>>>' + str(replacestr) + '<<<'
originalcontent[i] = str(replacestr) + '\n'
find_key = 'true'
if 'false' == find_key:
# print '>>>' + str(replacestr) + '<<<'
# print originalcontent
self.loggerchangeconfig.info('Now add %s' % str(replacestr))
originalcontent.append('\n' + str(replacestr))
find_separator = 'true'
if 'true' == find_separator:
break
except Exception, replace_err:
if replace_err:
pass
return originalcontent
def getlinenumber(self, num_list, first_line_num_list):
d_value1 = 0
last_first_num = 0
# print num_list
# print first_line_num_list
for num in first_line_num_list:
if num <= num_list[-1]:
d_value2 = int(num_list[-1]) - int(num)
if d_value2 < d_value1:
last_first_num = num
elif 0 == int(last_first_num):
last_first_num = num
d_value1 = d_value2
self.loggerchangeconfig.info('Modify xml\'s first line number is %d' % int(last_first_num))
return last_first_num
def backupconfig(self, path):
n = 0
backupfloder = self.backupfloder
self.loggerchangeconfig.info('Begin Backup \"%s\"' % path)
# Create backup floder
if not os.path.exists(backupfloder):
os.mkdir(backupfloder)
backupfloder_now = os.path.join(backupfloder, '%s' % self.time_now)
# Create backup Subfolder
if not os.path.exists(backupfloder_now):
os.mkdir(backupfloder_now)
while True:
backupfloder_now_new = os.path.join(backupfloder_now, 'backup%d' % n)
if not os.path.exists(backupfloder_now_new):
os.mkdir(backupfloder_now_new)
break
else:
n += 1
# Backup config file
if os.path.exists(path):
backupfile_path = os.path.join(backupfloder_now_new, os.path.basename(path))
shutil.copyfile(path, backupfile_path)
backup_path_path = os.path.join(backupfloder_now_new, 'path')
with open(backup_path_path, 'wb') as fw:
try:
# fw.write(path)
fw.write(path.encode('utf-8'))
except Exception, backup_err:
if backup_err:
fw.write(path)
self.loggerchangeconfig.info('Backup \"%s\" done' % path)
def restorebackup(self, timestamp):
backupfloder_restore = os.path.join(self.backupfloder, '%s' % timestamp)
if not os.path.exists(backupfloder_restore):
self.loggerchangeconfig.error('Can\'t find backup floder, please check your time stamp')
return
# restore the backup file
for p in range(0, len(os.listdir(backupfloder_restore))):
backup_file_path = os.path.join(backupfloder_restore, 'backup%d' % p)
with open(os.path.join(backup_file_path, 'path'), 'rb') as fr:
restore_path = fr.read()
try:
if 'AutoChangeConfig.xml' == os.path.basename(restore_path):
continue
self.loggerchangeconfig.info('Now restore \"%s\"' % restore_path)
shutil.copyfile(os.path.join(backup_file_path,
os.path.basename(restore_path)), restore_path.decode('utf-8'))
except Exception, restore_err:
if restore_err:
self.loggerchangeconfig.error(traceback.format_exc())
self.loggerchangeconfig.info('Restore config file done')
def lookupannotations(self, path, suffix):
with open(path, 'rb') as fr:
notes_str = fr.readlines()
if 'xml' == suffix:
c_list = []
d_list = []
for n in range(0, len(notes_str)):
if str(notes_str[n]).find('<!--') >= 0:
d_list.append(n)
if str(notes_str[n]).find('-->') >= 0:
d_list.append(n)
c_list.append(d_list)
d_list = []
# print c_list
self.xml_notes_list = c_list
elif 'ini' == suffix:
with open(path, 'rb') as fr:
ini_notes_str = fr.read()
c_list = []
d_list = []
notes_end_num = 0
while True:
notes_begin_num = ini_notes_str[notes_end_num:].find('#')
if notes_begin_num >= 0:
notes_begin_tmp_num = notes_begin_num + notes_end_num
d_list.append(notes_begin_tmp_num)
notes_end_num = ini_notes_str[notes_begin_tmp_num:].find('\n')
notes_end_tmp_num = notes_begin_tmp_num + notes_end_num
notes_end_num = notes_end_tmp_num
d_list.append(notes_end_tmp_num)
c_list.append(d_list)
d_list = []
else:
break
# print c_list
self.ini_notes_list = c_list
else:
c_list = []
d_list = []
for n in range(0, len(notes_str)):
if str(notes_str[n]).find('#') >= 0:
d_list.append(n)
d_list.append(n)
c_list.append(d_list)
d_list = []
# print c_list
self.other_notes_list = c_list
@staticmethod
def judgerange(num, lst):
for o in range(0, len(lst)):
if num >= lst[o][0]:
if num <= lst[o][1]:
return 'false'
def main(self):
try:
command1 = sys.argv[1]
if 'help' == command1 or '-help' == command1 or '--help' == command1 or '?' == command1 or '/?' == command1:
self.loggerchangeconfig.info('print help')
self.loggerchangeconfig.info('Please modify the configuration file as needed\n')
sys.exit()
elif str(command1).isdigit():
if 14 == len(command1):
self.restorebackup(command1)
else:
self.loggerchangeconfig.info('Please modify the configuration file as needed\n')
else:
self.readconfig()
except Exception, main_err:
if main_err:
self.readconfig()
if __name__ == '__main__':
a = AutoChangeConfig()
a.main()