【问题标题】:Replacing strings in YAML using Python使用 Python 替换 YAML 中的字符串
【发布时间】:2018-10-04 19:04:29
【问题描述】:

我有以下 YAML:

instance:
  name: test
  flavor: x-large
  image: centos7

tasks:
  centos-7-prepare:
    priority: 1
    details::
      ha: 0
      args:
        template: &startup
          name: startup-centos-7
          version: 1.2
        timeout: 1800

  centos-7-crawl:
    priority: 5
    details::
      ha: 1
      args:
        template: *startup
        timeout: 0

第一个任务定义模板名称和版本,然后由其他任务使用。模板定义不应更改,但其他尤其是任务名称会更改。

在 Python 中更改模板名称和版本的最佳方法是什么?

我有以下用于匹配的正则表达式(使用 re.DOTALL):

template:.*name: (.*?)version: (.*?)\s

但是到目前为止还没有弄清楚 re.sub 的使用情况。或者有没有更方便的方法来做到这一点?

【问题讨论】:

  • 这里不要使用正则表达式,使用用于解析 YAML 的库。
  • yaml 文件应该会在未来发生变化,所以解析的意义不大...

标签: python python-3.x yaml


【解决方案1】:

对于这种 YAML 的往返(加载-修改-转储),您应该使用 ruamel.yaml(免责声明:我是该软件包的作者)。

如果您输入的是input.yaml,那么您可以相对容易地找到 nameversiontemplate 键下并更新它们:

import sys
import ruamel.yaml

def find_template(d):
    if isinstance(d, list):
        for elem in d:
            x = find_template(elem)
            if x is not None:
                return x
    elif isinstance(d, dict):
        for k in d:
            v = d[k]
            if k == 'template':
                if 'name' in v and 'version' in v:
                    return v
            x = find_template(v)
            if x is not None:
                return x
    return None


yaml = ruamel.yaml.YAML()
# yaml.indent(mapping=4, sequence=4, offset=2)
yaml.preserve_quotes = True

with open('input.yaml') as ifp:
    data = yaml.load(ifp)
template = find_template(data)
template['name'] = 'startup-centos-8'
template['version'] = '1.3'

yaml.dump(data, sys.stdout)

给出:

instance:
  name: test
  flavor: x-large
  image: centos7

tasks:
  centos-7-prepare:
    priority: 1
    'details:':
      ha: 0
      args:
        template: &startup
          name: startup-centos-8
          version: '1.3'
        timeout: 1800

  centos-7-crawl:
    priority: 5
    'details:':
      ha: 1
      args:
        template: *startup
        timeout: 0

请注意,我在输入中插入的(多余的)引号,以及注释和别名的名称都被保留了。

【讨论】:

  • 效果很好。接受
  • 顺便说一句,您能否提供任何来源或详细说明为什么首选使用“ruamel.yaml”?干杯...
  • @Rezney documentation 包含其中的一些信息。简而言之:YAML 1.2 支持,往返而不丢失大量有用(对人类)信息,单一来源导致更容易修复错误(PyYAML 在 Python 2 和 3 的不同来源中有大量重复代码)。一个更好的 API,其中不安全的加载必须明确地完成
  • 谢谢。有没有办法保留未使用的锚?或者我应该为此打开一个单独的问题......
  • @Rezney 不,没有。已经有一个open issue
【解决方案2】:

我会将 yaml 文件解析成字典,然后编辑字段并将字典写回 yaml。

有关在 python How can I parse a YAML file in Python 中解析 yaml 的讨论,请参阅此问题,但我认为您最终会得到类似的结果。

from ruamel.yaml import YAML
from io import StringIO

yaml=YAML(typ='safe')
yaml.default_flow_style = False

#Parse from string
myConfig = yaml.load(doc)
#Example replacement code
for task in myConfig["tasks"]:
    if myConfig["tasks"][task]["details"]["args"]["template"]["name"] == "&startup":
        myConfig["tasks"][task]["details"]["args"]["template"]["name"] = "new value"
#Convert back to string
buf = StringIO()
yaml.dump(myConfig, buf)
updatedYml = buf.getvalue()

【讨论】:

  • 我在考虑 dict 但如果某些键名发生变化怎么办?它是为了自动化......
  • @Rezney 那么像centos-7-crawl 这样的属性名称可以更改吗?如果是这种情况,您可以查看每个字段并检查值是否是您要更改的值。
  • 是的,最有可能改变的。我会尝试考虑您的想法,但想避免盲目循环。还是谢谢...
  • 你使用哪个包?您链接到的问题的公认答案是使用 PyYAML。鉴于您的代码,我希望您不要这样做。
  • @EvanSnapp 我只是想确保您没有使用 PyYAML 中可能不安全的 yaml.load()。如此多的答案提出,没有意识到它可能是不安全的,并且完全没有必要使用它来代替safe_load()
猜你喜欢
  • 1970-01-01
  • 2022-11-25
  • 2016-09-17
  • 2022-11-17
  • 2019-12-21
  • 1970-01-01
  • 2020-05-16
  • 1970-01-01
  • 2019-10-01
相关资源
最近更新 更多