如何替换YAML文件中的许多相同值

时间:2018-11-29 16:30:54

标签: python yaml pyyaml

我目前正在构建一个使用YAML配置的python应用程序。我通过使用其他YAML文件生成YAML配置文件。我有一个“模板” YAML,它定义了我要在应用程序使用的YAML文件中使用的基本结构,然后定义了许多不同的“数据” YAML,它们填充了模板以某种方式旋转应用程序的行为。例如,说我有10个“数据” YAML。根据部署应用程序的位置,选择1个“数据” YAML,并用于填写“模板” YAML。结果填写的YAML是应用程序用来运行的内容。这节省了我很多工作。我遇到了这种方法的问题。假设我有一个YAML模板,如下所示:

{
    "extends": "../../tsconfig-base.json",
    "include": [
        "**/*.ts"
    ],
    "outDir": "built/shared",
    "compilerOptions": {
        "module": "amd",
        "types": []
    }
}

然后在其他地方,我喜欢10个“数据” YAML,每个YAML具有不同的{{id}}值。我似乎无法找出一种有效的方法来替换模板中所有这些{{id}}的出现。我有一个问题,因为有时要替换的值是我想主要保留的值的子字符串,或者出现的位置在层次结构中彼此相距很远,从而导致循环解决方案效率低下。我当前使用template + data生成配置文件的方法在python中看起来像这样:

id: {{id}}
endpoints:
  url1: https://website.com/{{id}}/search
  url2: https://website.com/foo/{{id}}/get_thing
  url3: https://website.com/hello/world/{{id}}/trigger_stuff
foo:
  bar:
    deeply:
      nested: {{id}}

关于如何更快地解决所有{{id}}事件的想法吗?

3 个答案:

答案 0 :(得分:1)

如果单个id文件的每个位置yaml都相同,那么您可以将模板作为纯文本读取并逐行使用字符串替换。

new_file = []

# New id for replacement (from loaded file)
id_ = '123'

# Open template file 
with open('template.yaml', 'r') as f:
    # Iterate through each line
    for l in f:
        # Replace every {{id}} occurrence
        new_file.append(l.replace('{{id}}', id_))

# Save the new file
with open('new_file.yaml', 'w') as f:
    for l in new_file:
        f.write(l)

这会将{{id}}替换为文件中各处的相同id_,并且不会更改任何格式。

答案 1 :(得分:0)

YAML内置了“锚”,您可以制作它并引用类似的变量。在我看来,这些实际上不是在替换它们的值,因为在解析YAML之后您只会看到结果。代码是从Reddit post偷偷窃取的,其中涉及类似的主题:

# example.yaml
params: &params
  PARAM1: &P1 5
  PARAM2: &P2 "five"
  PARAM3: &P3 [*P1, *P2]

data:
  <<: *params
  more:
    - *P3
    - *P2

ff

# yaml.load(example) =>
{
'params': {
    'PARAM1': 5, 
    'PARAM2': 'five', 
    'PARAM3': [5, 'five']
},
'data': {
    'PARAM1': 5,
    'PARAM2': 'five',
    'PARAM3': [5, 'five'],
    'more': [[5, 'five'], 'five']
}
}

this上的帖子是我认为您可以将锚用作子字符串的方式(假设您使用的是python)

答案 2 :(得分:0)

您正在向我们建议PyYAML,但它并不非常适合 YAML文件上的更新。在此过程中,是否可以将文件加载到 首先,您松开映射键顺序,保留所有注释 在文件中包含,合并会扩展,以及任何特殊的锚点名称 在翻译中迷路。除此之外,PyYAML无法处理 最新的YAML规范(9年前发布),并且只能处理简单的映射键。

主要有两种解决方案:

  • 您可以在原始文件上使用替换
  • 您使用ruamel.yaml并递归到数据结构中

替换

如果使用替换,则可以比使用替换更有效的方式 @caseWestern建议的逐行替换。但是大多数 总而言之,您应该强化这些替代所采用的标量 地点。当前您有普通标量(即流式标量) 如果您插入诸如 #:和其他语法上重要的元素。

为了防止这种情况发生,请重写输入文件以供使用 块样式文字标量:

id: {{id}}
endpoints:
  url1: |-
    https://website.com/{{id}}/search
  url2: |-
    https://website.com/foo/{{id}}/get_thing
  url3: |-
    https://website.com/hello/world/{{id}}/trigger_stuff
foo:
  bar:
    deeply:
      nested: |-
        {{id}}

如果以上内容位于alt.yaml中,您可以执行以下操作:

val = 'xyz'

with open('alt.yaml') as ifp:
    with open('new.yaml', 'w') as ofp:
       ofp.write(ifp.read().replace('{{id}}', val))

获得:

id: xyz
endpoints:
  url1: |-
    https://website.com/xyz/search
  url2: |-
    https://website.com/foo/xyz/get_thing
  url3: |-
    https://website.com/hello/world/xyz/trigger_stuff
foo:
  bar:
    deeply:
      nested: |-
        xyz

ruamel.yaml

使用ruamel.yaml(免责声明:我是该软件包的作者),您不必 担心通过语法上重要的替换文本来破坏输入。如果 这样做,输出将自动正确引用。你一定要 请确保您输入的内容是有效的YAML,并使用{{之类的 节点的开头表示两个嵌套的流样式映射,您会遇到麻烦。

这里的最大优点是您的输入文件已加载,并且被检查为 正确的YAML。但这比文件级替换慢得多。

因此,如果您输入的是in.yaml

id: <<id>>  # has to be unique
endpoints: &EP
  url1: https://website.com/<<id>>/search
  url2: https://website.com/foo/<<id>>/get_thing
  url3: https://website.com/hello/world/<<id>>/trigger_stuff
foo:
  bar:
    deeply:
      nested: <<id>>
    endpoints: *EP
    [octal, hex]: 0o123, 0x1F

您可以这样做:

import sys
import ruamel.yaml

def recurse(d, pat, rep):
    if isinstance(d, dict):
        for k in d:
            if isinstance(d[k], str):
                d[k] = d[k].replace(pat, rep)
            else:
               recurse(d[k], pat, rep)
    if isinstance(d, list):
        for idx, elem in enumerate(d):
            if isinstance(elem, str):
                d[idx] = elem.replace(pat, rep)
            else:
               recurse(d[idx], pat, rep)


yaml = ruamel.yaml.YAML()
yaml.preserve_quotes = True
with open('in.yaml') as fp:
    data = yaml.load(fp)
recurse(data, '<<id>>', 'xy: z')  # not that this makes much sense, but it proves a point
yaml.dump(data, sys.stdout)

给出:

id: 'xy: z' # has to be unique
endpoints: &EP
  url1: 'https://website.com/xy: z/search'
  url2: 'https://website.com/foo/xy: z/get_thing'
  url3: 'https://website.com/hello/world/xy: z/trigger_stuff'
foo:
  bar:
    deeply:
      nested: 'xy: z'
    endpoints: *EP
    [octal, hex]: 0o123, 0x1F

请注意:

  • 具有替换模式的值在转储时自动引用到 处理: +空格,否则将表示映射并破坏YAML

  • 与PyYAML的YAML.load()函数相反的load方法是 安全(即无法通过操纵输入来执行任意Python 文件。

  • 注释,八进制和十六进制整数以及别名被保留。

  • PyYAML尽管有效,但根本无法加载文件in.yaml

  • 上面的recurse仅更改输入映射值, 如果您还想做按键,则必须弹出 重新插入所有密钥(即使没有更改),以保持原始状态 订单,或者您需要使用enumerated.insert(position, key, value)。如果有合并,您也不能只遍历键, 您将不得不遍历“ dict”的非合并键。