使用 Bash 编辑 YAML 文件
Posted
技术标签:
【中文标题】使用 Bash 编辑 YAML 文件【英文标题】:Edit YAML file with Bash 【发布时间】:2020-12-14 06:57:59 【问题描述】:我正在尝试编辑以下 YAML 文件
db:
host: 'x.x.x.x.x'
main:
password: 'password_main'
admin:
password: 'password_admin'
要编辑 host
部分,我可以使用它
sed -i "/^\([[:space:]]*host: \).*/s//\1'$DNS_ENDPOINT'/" config.yml
但我找不到更新main
和admin
密码的方法(它们是不同的值)。
我试着玩弄\n
和[[:space:]]
并得到了不同的口味:
sed -i "/^\([[:space:]]*main:\n*[[:space:]]*password: \).*/s//\1'$DNS_ENDPOINT'/" config.yml
但从来没有让它工作。 非常感谢任何帮助!
编辑 - 要求:没有外部二进制文件/工具。只是很好的狂欢。
【问题讨论】:
你应该考虑使用像yq
这样的YAML解析器你能安装这样的工具吗?
我宁愿避免这样做。不是要求,但差不多。
所以yq
有两个版本可用。一个使用jq
,另一个使用适当的 DSL,用 Go 编写。前者-kislyuk.github.io/yq,后者-github.com/mikefarah/yq。取决于你想使用哪一个
记住 sed != bash,因此无论您采用哪种方法,自定义标志/正则表达式都可能在一个系统上工作而在另一个系统上失败
Just good ol' bash
将是一个糟糕的选择。如果您不能使用yq
,那么您应该使用可以从外壳调用的标准工具之一,例如awk。
【参考方案1】:
由于您不想安装yq
,您可以使用您很可能已经安装的python。
以下是基本原理:
#!/usr/bin/python
import yaml
with open("file.yml") as f:
y=yaml.safe_load(f)
y['db']['admin']['password'] = 'new_admin_pass'
print(yaml.dump(y, default_flow_style=False, sort_keys=False))
输出:
db:
host: x.x.x.x.x
main:
password: password_main
admin:
password: new_admin_pass
类似的python 代码作为单行代码,您可以放入bash 脚本中,看起来像这样(并产生相同的输出):
python -c 'import yaml;f=open("file.yml");y=yaml.safe_load(f);y["db"]["admin"]["password"] = "new_admin_pass"; print(yaml.dump(y, default_flow_style=False, sort_keys=False))'
【讨论】:
@RavinderSingh13 欢迎您!它需要一些编辑来替换硬编码的字符串,但在编辑 YAML 文件时它应该是一个很好的基础。 确实是非常好的答案,为我提供 Python 的健壮性。【参考方案2】:注意:使用像yq
(或yq
)这样的YAML解析器将是一种更可靠的解决方案。
但是,我已经使用以下“技术”来更改“预定义”行,尽管 grep 和 sed 的帮助是这样的;
/tmp/config.yml
db:
host: 'x.x.x.x.x'
main:
password: 'password_main'
admin:
password: 'password_admin'
-
获取“旧密码”所在的行号:
grep -n 'password_admin' /tmp/config.yml | cut -d ':' -f1
然后,使用6
sed
用您的新密码覆盖该行:
sed -i '6s/.*/ password: \'new_admin_pass\'/' /tmp/config.yml
新文件现在如下所示:
db:
host: 'x.x.x.x.x'
main:
password: 'password_main'
admin:
password: 'new_admin_pass'
注意
请记住,密码中的任何特殊字符(&
、\
、/
)都会导致 sed
行为不端!
如果缩进发生变化,这可能会失败,因为 YAML 关心缩进。就像我上面提到的,使用 YAML 解析器将是一个更可靠的解决方案!
【讨论】:
谢谢!让我们假设缩进没有改变。其他原因会导致此命令失败吗? @Mornor 我不这么认为,我用这种“风格”来改变 YAML 已经有一段时间了,从来没有失败过。但是,我的 YAML 文件是非常静态的,我只是使用这种技术来更改密码;) 如果新密码包含反向引用元字符,如&
或 \1
或如果它包含分隔符字符 /
或转义字符 my\new\pass
或其他,它将失败。只要您的密码始终是严格的字母数字,您应该可以,但如果不是,则 YMMV。不过,您不需要 grep+cut+sed - 一个简单的 awk 脚本就可以完成这项工作。并不是说这取决于您知道旧的管理员密码,而您真正想要的只是将管理员(或主)密码更改为新值,无论以前是什么。
... 我刚刚意识到,如果 main:
密码恰好与 admin:
密码相同,或者该管理员密码恰好也出现在文件。【参考方案3】:
$ awk -v new="'sumthin'" 'prev=="main:"sub(/\047.*/,""); $0=$0 new prev=$1 1' file
db:
host: 'x.x.x.x.x'
main:
password: 'sumthin'
admin:
password: 'password_admin'
或者,如果您的新文本可能包含您不想扩展的转义序列(例如 \t
或 \n
),这在设置密码时很可能出现,那么:
new="'sumthin'" awk 'prev=="main:"sub(/\047.*/,""); $0=$0 ENVIRON["new"] prev=$1 1' file
请参阅 How do I use shell variables in an awk script?,了解我为什么/如何使用 ENVIRON[]
访问 shell 变量,而不是在第二个脚本中设置 awk 变量。
【讨论】:
【参考方案4】:这没有yq
可靠,但如果您的 yaml 文件结构与问题中显示的相同,则可以使用此awk
:
pw='new_&pass'
awk -v pw="$pw//&/\\\\&" '/^[[:blank:]]*main:/
print
if (getline > 0 && $1 == "password:")
sub(/\047[^\047]*\047/, "\047" pw "\047")
1' file
db:
host: 'x.x.x.x.x'
main:
password: 'new_&pass'
admin:
password: 'password_admin'
【讨论】:
【参考方案5】:正如专家在其他答案中所提到的那样,yq
应该是正确的方法,但如果有人没有它,那么可以尝试关注。
awk -v s1="'" -v new_pass="new_value_here" '
/main:/
main_found=1
print
next
main_found && /password/
next
/admin:/ && main_found
print " password: " s1 new_pass s1 ORS $0
main_found=""
next
1
' Input_file
注意:如果您想将输出保存到 Input_file 本身,请将 > temp && mv temp Input_file
添加到上述解决方案中。
【讨论】:
以上是关于使用 Bash 编辑 YAML 文件的主要内容,如果未能解决你的问题,请参考以下文章