[英]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: ¶ms
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年前發布),它只能處理簡單的映射鍵。
主要有兩種解決方案:
如果你使用替代,你可以比@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(免責聲明:我是該軟件包的作者),您不必擔心語法上重要的替換文本會破壞輸入。 如果這樣做,則輸出將自動被正確引用。 您必須注意您的輸入是有效的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
,只改變了輸入的映射值,如果你也想要做鍵,你要么必須彈出並重新插入所有鍵(即使沒有改變),要保持原始順序,要么你需要使用enumerate
和d.insert(position, key, value)
。 如果你有合並,你也不能只是走過鑰匙,你必須走過“dict”的非合並鍵。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.