正则表达式更新多行字符串并保留缩进
regex to update multi-lined string and preserve indentation
我有数百个 YAML 文件需要不时更新。
更新前:
sss:
- ccc:
brr: 'mmm'
jdk: 'openjdk8'
- bbb:
brr: 'rel/bbb'
jdk: 'openjdk8'
- aaa:
brr: 'rel/aaa'
jdk: 'openjdk7'
更新后:
sss:
- ddd:
brr: 'mmm'
jdk: 'openjdk8'
- ccc:
brr: 'rel/ccc'
jdk: 'openjdk8'
- bbb:
brr: 'rel/bbb'
jdk: 'openjdk8'
- aaa:
brr: 'rel/aaa'
jdk: 'openjdk7'
- 对于任何文件中出现的以下模式:
sss:
- ccc:
brr: 'mmm'
- 替换并修改上述模式,将
'mmm'
替换为 'rel/ccc'
:
- ccc:
brr: 'rel/ccc'
- 以以下格式创建新的子字符串(多行):
- new:
brr: 'new-mmm'
jdk: 'openjdk8'
- 合并 2. 和 3. 并将原始文件替换为:
sss:
- new:
brr: 'mmm'
jdk: 'openjdk8'
- ccc:
brr: 'rel/ccc'
jdk: 'openjdk8'
例如,我们需要更新上述文件,使其看起来像在每一行中保留白色 spaces/tabs,因为格式化对于 YAML 很重要。
我已经用 PyYAML 尝试过,但由于语法复杂而无法正常工作。这可以通过使用 awk, sed 捕获空格来完成吗?
试试这个 awk
程序:
/sss:/ { sss = 1; }
/- ccc:/ { ccc = 1; ind = substr([=10=], 1, index([=10=], "-")-1); next; } # don't print
== "brr:" && == "'mmm'" {
if (sss && ccc) {
print ind "- ddd:";
print ind " brr: 'mmm'";
print ind " jdk: 'openjdk8'";
print ind "- ccc:";
print ind " brr: 'rel/ccc'";
sss = 0; ccc = 0;
}
next;
}
{ print }
第一条规则用于标记进入sss
块,第二条用于标记ccc
块,另外记录缩进深度。第三条规则添加新的和修改的数据,根据记录的深度缩进,然后退出 sss
和 ccc
块。最终规则打印刚刚读取的行。第二条和第三条规则中的 next
语句阻止应用以下所有规则。
解析结构化数据,无论是 YAML、HTML、XML 还是 CSV,仅使用正则表达式仅适用于一小部分可能情况。使用 YAML 多行标量,以通用方式处理流式和块式等几乎是不可能的。如果不是这种情况,那么早就有人用 awk 编写了完整的 YAML 解析器。 (awk
没有错,只是不是处理 YAML 的正确工具。
这并不意味着您不能使用正则表达式来查找特定元素,您只需要做一些准备:
import sys
import re
import ruamel.yaml
yaml_str = """\
sss:
- ccc:
brr: 'mmm'
jdk: 'openjdk8'
- bbb:
brr: 'rel/bbb'
jdk: 'openjdk8'
- aaa:
brr: 'rel/aaa'
jdk: 'openjdk7'
"""
class Paths:
def __init__(self, data, sep=':'):
self._sep = sep
self._data = data
def walk(self, data=None, prefix=None):
if data is None:
data = self._data
if prefix is None:
prefix = []
if isinstance(data, dict):
for idx, k in enumerate(data):
path_list = prefix + [k]
yield self._sep.join([str(q) for q in path_list]), path_list, idx, data[k]
for x in self.walk(data[k], path_list):
yield x
elif isinstance(data, list):
for idx, k in enumerate(data):
path_list = prefix + [idx]
yield self._sep.join([str(q) for q in path_list]), path_list, idx, k
for x in self.walk(k, path_list):
yield x
def set(self, pl, val):
pl = pl[:]
d = self._data
while(len(pl) > 1):
d = d[pl.pop(0)]
d[pl[0]] = val
def insert_in_list(self, pl, idx, val):
pl = pl[:]
d = self._data
while(len(pl) > 1):
d = d[pl.pop(0)]
d.insert(idx, val)
data = ruamel.yaml.round_trip_load(yaml_str, preserve_quotes=True)
paths = Paths(data)
pattern = re.compile('sss:.*:c.*:brr$')
# if you are going to insert/delete use list(paths.walk())
for p, pl, idx, val in list(paths.walk()):
print('path', p)
if not pattern.match(p):
continue
paths.set(pl, ruamel.yaml.scalarstring.SingleQuotedScalarString('rel/ccc'))
paths.insert_in_list(pl[:-2], idx, {'new': {
'brr': ruamel.yaml.scalarstring.SingleQuotedScalarString('mmm'),
'jdk': ruamel.yaml.scalarstring.SingleQuotedScalarString('openjdk8')
}})
print('----------')
ruamel.yaml.round_trip_dump(data, sys.stdout)
输出为:
path sss
path sss:0
path sss:0:ccc
path sss:0:ccc:brr
path sss:0:ccc:jdk
path sss:1
path sss:1:bbb
path sss:1:bbb:brr
path sss:1:bbb:jdk
path sss:2
path sss:2:aaa
path sss:2:aaa:brr
path sss:2:aaa:jdk
----------
sss:
- new:
brr: 'mmm'
jdk: 'openjdk8'
- ccc:
brr: 'rel/ccc'
jdk: 'openjdk8'
- bbb:
brr: 'rel/bbb'
jdk: 'openjdk8'
- aaa:
brr: 'rel/aaa'
jdk: 'openjdk7'
打印 "paths" 不是必需的,但在这里可以更好地了解发生了什么。
SingleQuotedScalarString 是获取 YAML 输出中字符串标量周围多余引号所必需的
字典子类,YAML 映射由 ruamel.yaml
加载,支持 Python 2.7 和 Python 3.5 及更高版本的 .insert(index, key, val)
,所以你也可以在映射的特定位置插入。
我有数百个 YAML 文件需要不时更新。
更新前:
sss:
- ccc:
brr: 'mmm'
jdk: 'openjdk8'
- bbb:
brr: 'rel/bbb'
jdk: 'openjdk8'
- aaa:
brr: 'rel/aaa'
jdk: 'openjdk7'
更新后:
sss:
- ddd:
brr: 'mmm'
jdk: 'openjdk8'
- ccc:
brr: 'rel/ccc'
jdk: 'openjdk8'
- bbb:
brr: 'rel/bbb'
jdk: 'openjdk8'
- aaa:
brr: 'rel/aaa'
jdk: 'openjdk7'
- 对于任何文件中出现的以下模式:
sss: - ccc: brr: 'mmm'
- 替换并修改上述模式,将
'mmm'
替换为'rel/ccc'
:
- ccc: brr: 'rel/ccc'
- 以以下格式创建新的子字符串(多行):
- new: brr: 'new-mmm' jdk: 'openjdk8'
- 合并 2. 和 3. 并将原始文件替换为:
sss: - new: brr: 'mmm' jdk: 'openjdk8' - ccc: brr: 'rel/ccc' jdk: 'openjdk8'
例如,我们需要更新上述文件,使其看起来像在每一行中保留白色 spaces/tabs,因为格式化对于 YAML 很重要。
我已经用 PyYAML 尝试过,但由于语法复杂而无法正常工作。这可以通过使用 awk, sed 捕获空格来完成吗?
试试这个 awk
程序:
/sss:/ { sss = 1; }
/- ccc:/ { ccc = 1; ind = substr([=10=], 1, index([=10=], "-")-1); next; } # don't print
== "brr:" && == "'mmm'" {
if (sss && ccc) {
print ind "- ddd:";
print ind " brr: 'mmm'";
print ind " jdk: 'openjdk8'";
print ind "- ccc:";
print ind " brr: 'rel/ccc'";
sss = 0; ccc = 0;
}
next;
}
{ print }
第一条规则用于标记进入sss
块,第二条用于标记ccc
块,另外记录缩进深度。第三条规则添加新的和修改的数据,根据记录的深度缩进,然后退出 sss
和 ccc
块。最终规则打印刚刚读取的行。第二条和第三条规则中的 next
语句阻止应用以下所有规则。
解析结构化数据,无论是 YAML、HTML、XML 还是 CSV,仅使用正则表达式仅适用于一小部分可能情况。使用 YAML 多行标量,以通用方式处理流式和块式等几乎是不可能的。如果不是这种情况,那么早就有人用 awk 编写了完整的 YAML 解析器。 (awk
没有错,只是不是处理 YAML 的正确工具。
这并不意味着您不能使用正则表达式来查找特定元素,您只需要做一些准备:
import sys
import re
import ruamel.yaml
yaml_str = """\
sss:
- ccc:
brr: 'mmm'
jdk: 'openjdk8'
- bbb:
brr: 'rel/bbb'
jdk: 'openjdk8'
- aaa:
brr: 'rel/aaa'
jdk: 'openjdk7'
"""
class Paths:
def __init__(self, data, sep=':'):
self._sep = sep
self._data = data
def walk(self, data=None, prefix=None):
if data is None:
data = self._data
if prefix is None:
prefix = []
if isinstance(data, dict):
for idx, k in enumerate(data):
path_list = prefix + [k]
yield self._sep.join([str(q) for q in path_list]), path_list, idx, data[k]
for x in self.walk(data[k], path_list):
yield x
elif isinstance(data, list):
for idx, k in enumerate(data):
path_list = prefix + [idx]
yield self._sep.join([str(q) for q in path_list]), path_list, idx, k
for x in self.walk(k, path_list):
yield x
def set(self, pl, val):
pl = pl[:]
d = self._data
while(len(pl) > 1):
d = d[pl.pop(0)]
d[pl[0]] = val
def insert_in_list(self, pl, idx, val):
pl = pl[:]
d = self._data
while(len(pl) > 1):
d = d[pl.pop(0)]
d.insert(idx, val)
data = ruamel.yaml.round_trip_load(yaml_str, preserve_quotes=True)
paths = Paths(data)
pattern = re.compile('sss:.*:c.*:brr$')
# if you are going to insert/delete use list(paths.walk())
for p, pl, idx, val in list(paths.walk()):
print('path', p)
if not pattern.match(p):
continue
paths.set(pl, ruamel.yaml.scalarstring.SingleQuotedScalarString('rel/ccc'))
paths.insert_in_list(pl[:-2], idx, {'new': {
'brr': ruamel.yaml.scalarstring.SingleQuotedScalarString('mmm'),
'jdk': ruamel.yaml.scalarstring.SingleQuotedScalarString('openjdk8')
}})
print('----------')
ruamel.yaml.round_trip_dump(data, sys.stdout)
输出为:
path sss
path sss:0
path sss:0:ccc
path sss:0:ccc:brr
path sss:0:ccc:jdk
path sss:1
path sss:1:bbb
path sss:1:bbb:brr
path sss:1:bbb:jdk
path sss:2
path sss:2:aaa
path sss:2:aaa:brr
path sss:2:aaa:jdk
----------
sss:
- new:
brr: 'mmm'
jdk: 'openjdk8'
- ccc:
brr: 'rel/ccc'
jdk: 'openjdk8'
- bbb:
brr: 'rel/bbb'
jdk: 'openjdk8'
- aaa:
brr: 'rel/aaa'
jdk: 'openjdk7'
打印 "paths" 不是必需的,但在这里可以更好地了解发生了什么。
SingleQuotedScalarString 是获取 YAML 输出中字符串标量周围多余引号所必需的
字典子类,YAML 映射由
ruamel.yaml
加载,支持 Python 2.7 和 Python 3.5 及更高版本的.insert(index, key, val)
,所以你也可以在映射的特定位置插入。