繁体   English   中英

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

[英]How to replace many identical values in a YAML file

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

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}}

然后在其他地方,我有10个“数据”YAML,每个YAML都有{{id}}的不同值。 我似乎无法找到一种有效的方法来替换模板中所有这些{{id}}次出现。 我遇到了一个问题,因为有时要替换的值是我想要保留的值的子字符串,或者层次结构中出现的距离非常远,使得循环解决方案效率低下。 我目前使用模板+数据生成配置文件的方法在python中看起来像这样:

import yaml
import os

template_yaml = os.path.abspath(os.path.join(os.path.dirname(__file__), 'template.yaml'))
# In this same folder you would find flavor2, flavor3, flavor4, etc, lets just use 1 for now
data_yaml = os.path.abspath(os.path.join(os.path.dirname(__file__), 'data_files', 'flavor1.yaml'))
# This is where we dump the filled out template the app will actually use
output_directory = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))

with open(template_yaml, 'r') as template:
    try:
        loaded_template = yaml.load(template)  # Load the template as a dict
        with open(data_yaml , 'r') as data:
            loaded_data= yaml.load(data)  # Load the data as a dict
        # From this point on I am basically just setting individual keys from "loaded_template" to values in "loaded_data"
        # But 1 at a time, which is what I am trying to avoid:
        loaded_template['id'] = loaded_data['id']
        loaded_template['endpoints']['url1'] = loaded_template['endpoints']['url1'].format(loaded_data['id'])
        loaded_template['foo']['bar']['deeply']['nested'] = loaded_data['id']

有关如何更快地更改所有{{id}}次事件的任何想法吗?

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

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_ ,并且不会更改任何格式。

YAML内置了“锚点”,您可以制作并引用类似变量。 对我来说,这些实际上是在替换引用它们的值时并不明显,因为在解析YAML之后只能看到结果。 代码在一篇涉及类似主题的Reddit帖子中被无耻地窃取:

# 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']
}
}

这篇关于SO的帖子就是我认为你可以将锚点用作子串(假设你使用的是python)

您向我们提议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的load函数相反, YAML.load()方法是安全的(即不能通过操作输入文件来执行任意Python。

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

  • PyYAML根本无法加载文件in.yaml ,尽管它是有效的YAML

  • 上面的recurse ,只改变了输入的映射值,如果你也想要做键,你要么必须弹出并重新插入所有键(即使没有改变),要保持原始顺序,要么你需要使用enumerated.insert(position, key, value) 如果你有合并,你也不能只是走过钥匙,你必须走过“dict”的非合并键。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM