簡體   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