正则表达式更新多行字符串并保留缩进

时间:2017-03-10 06:45:48

标签: awk sed yaml pyyaml

我有几百个YAML文件需要不时更新。

更新前:

peekAll

更新后:

sss:
  - ccc:
      brr: 'mmm'
      jdk: 'openjdk8'
  - bbb:
      brr: 'rel/bbb'
      jdk: 'openjdk8'
  - aaa:
      brr: 'rel/aaa'
      jdk: 'openjdk7'
  1. 任何文件中出现以下模式:
  2. sss:
      - ddd: 
          brr: 'mmm'
          jdk: 'openjdk8'
      - ccc:
          brr: 'rel/ccc'
          jdk: 'openjdk8'
      - bbb:
          brr: 'rel/bbb'
          jdk: 'openjdk8'
      - aaa:
          brr: 'rel/aaa'
          jdk: 'openjdk7'
    
    1. 替换并修改上述模式,将sss: - ccc: brr: 'mmm' 替换为'mmm'
    2. 'rel/ccc'
      1. 以下列格式创建新的子字符串(多行):
      2.   - ccc:
              brr: 'rel/ccc'
        
        1. 组合2.和3.并将原始文件替换为:
        2.   - new: 
                brr: 'new-mmm'
                jdk: 'openjdk8'
          

          例如,我们需要更新上面的文件,看起来像保留每行中的空格/制表符,因为格式化对于YAML很重要。

          我已经使用PyYAML尝试了这个,并且由于语法的复杂性而无法工作。可以通过使用awk,sed?

          捕获空格来完成

2 个答案:

答案 0 :(得分:2)

尝试使用此类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块,另外还用于记录缩进深度。第三个规则添加新数据和修改后的数据,根据记录的深度缩进,然后退出sssccc块。最终规则打印刚刚读取的行。第二和第三条规则中的next语句阻止应用所有后续规则。

答案 1 :(得分:1)

解析结构化数据,无论是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'
  1. 打印"路径"没有必要,但这里是为了更好地了解正在发生的事情。

  2. SingleQuotedScalarString是获取YAML输出中字符串标量周围多余引号所必需的

  3. ruamel.yaml加载YAML映射的dict子类支持Python 2.7和Python 3.5及更高版本的.insert(index, key, val),因此您也可以插入映射的特定位置