【问题标题】:regex to update multi-lined string and preserve indentation正则表达式更新多行字符串并保留缩进
【发布时间】:2017-03-10 06:45:47
【问题描述】:

我有数百个 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'
  1. 对于任何文件中出现以下模式的任何情况:
sss:
  - ccc:
      brr: 'mmm'
  1. 替换并修改上述模式,将'mmm'替换为'rel/ccc'
  - ccc:
      brr: 'rel/ccc'
  1. 按以下格式创建新的子字符串(多行):
  - new: 
      brr: 'new-mmm'
      jdk: 'openjdk8'
  1. 结合 2. 和 3. 并将原始文件替换为:
sss:
  - new: 
      brr: 'mmm'
      jdk: 'openjdk8'
  - ccc:
      brr: 'rel/ccc'
      jdk: 'openjdk8'

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

我已经用 PyYAML 尝试过这个,但由于语法的复杂性,它不起作用。这可以通过使用 awk, sed 捕获空白来完成吗?

【问题讨论】:

  • 你可以看看SO: Shell scripting - split xml into multiple files。在那里您可以看到如何使用变量在多种模式之间“切换 awk 解析”。
  • 您需要提供更多有关替换/添加的信息。添加不存在的第二级很容易理解。例如:您将它放在节的开头,但可以将其添加到同一节的末尾吗?
  • 我们可以假设从示例 2 中删除 sss: 是一个错误吗?
  • 捕获空白是什么意思? brr: 'mmm' 的缩进级别是否足以识别所有需要替换的位置?
  • @Anthon: sss 可以被删除,但它必须用作搜索字符串的起点。因此,缩进可能会有所不同,我想捕获空格/制表符并在更新的多行字符串中重新引入它们。

标签: awk sed yaml pyyaml


【解决方案1】:

试试这样的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 语句阻止应用以下所有规则。

【讨论】:

  • 输出正确,但没有缩进。缩进可以保留吗?
  • 没有缩进是什么意思?
  • 我已根据对您言论的理解更新了我的回答。
【解决方案2】:

仅使用正则表达式解析结构化数据(无论是 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),因此您也可以在映射的特定位置插入。

【讨论】:

  • @askb 在复制粘贴过程中,python 文件的顶部丢失了。我重新添加了。
  • 我之前使用过pyaml,但我遇到了同样的问题。这是一个 sn-p:paste.openstack.org/show/602443 这不会读取以特殊字符开头的 yaml 行(!):ruamel.yaml.constructor.ConstructorError: could not determine a constructor for the tag '!include-raw:' 是有解决方法吗?
  • @askb 这是一个不同的问题。您必须为文件中带有!include-raw 等标签的对象提供构造函数,以及再次转储标签的表示器。只需将其设为 dict 的子类,但这是新的和不同的问题,不是 cmets 需要解决的问题。
  • 如果感叹号在引号中,则无需执行任何操作。错误消息来自文件中未加引号的感叹号(即键 shell 的值)
  • 创建一个通用的构造函数/表示器应该是可行的,我已经为泡菜做了类似的事情。我现在很忙,但我会在今晚或本周晚些时候看看。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-13
  • 1970-01-01
  • 2021-11-27
  • 1970-01-01
相关资源
最近更新 更多