Awk 正则表达式更新多行字符串并保留缩进
我有数百个YAML文件需要不时更新 更新前:Awk 正则表达式更新多行字符串并保留缩进,awk,sed,yaml,pyyaml,Awk,Sed,Yaml,Pyyaml,我有数百个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:
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'
'mmm'
替换为'rel/ccc'
:我已经用PyYAML尝试过了,但是由于语法的复杂性,它不起作用。这可以通过使用awk,sed捕获空白来实现吗?尝试类似于
awk
程序的方法:
/sss:/ { sss = 1; }
/- ccc:/ { ccc = 1; ind = substr($0, 1, index($0, "-")-1); next; } # don't print
$1 == "brr:" && $2 == "'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
语句阻止应用以下所有规则。尝试类似的awk
程序:
/sss:/ { sss = 1; }
/- ccc:/ { ccc = 1; ind = substr($0, 1, index($0, "-")-1); next; } # don't print
$1 == "brr:" && $2 == "'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'
ruamel.YAML
加载YAML映射,它支持Python 2.7和Python 3.5及更高版本的.insert(index,key,val)
,因此您也可以在映射的特定位置插入单独使用正则表达式解析结构化数据(无论是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'
ruamel.YAML
加载YAML映射,它支持Python 2.7和Python 3.5及更高版本的.insert(index,key,val)
,因此您也可以在映射的特定位置插入你可以看一看。在这里,您可以看到如何使用变量在多个模式之间“切换awk解析”。您需要提供有关替换/添加的更多信息。添加第二级不存在的内容很容易理解。例:您将其放在章节的开头,但是否可以将其添加到同一章节的末尾?我们是否可以假设从示例2中删除
sss:
是一个错误?您捕获空白是什么意思?brr:'mmm'
的缩进级别是否足以识别所有需要替换的位置?@Anthon:sss可以删除,但必须用作搜索字符串的起点。缩进可能不同,因此,我想捕获空格/制表符,并在更新的多行字符串中重新引入它们。在这里,您可以看到如何使用变量在多个模式之间“切换awk解析”。您需要提供有关替换/添加的更多信息。添加第二级不存在的内容很容易理解。例:您将其放在章节的开头,但是否可以将其添加到同一章节的末尾?我们是否可以假设从示例2中删除sss:
是一个错误?您捕获空白是什么意思?brr:'mmm'
的缩进级别是否足以识别所有需要替换的位置?@Anthon:sss可以删除,但必须用作搜索字符串的起点。缩进可能不同,因此,我希望捕获空格/制表符,并在更新的多行字符串中重新引入它们。输出是正确的,但没有缩进。缩进可以保留吗?没有缩进是什么意思?根据我对你的评论的理解,我已经更新了我的答案。输出是正确的,但没有缩进。缩进可以保留吗?没有缩进是什么意思?我已经根据我的理解更新了我的答案
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'