我有一个 YAML 文件,其中包含一个如下所示的节:
admin::common::passwords:
alice:
password: '$6$oTQhLvN/4VFJPscD$8LYwUMSFi'
bob:
password: '$6$JKOtLF0wHeZfIskt$W/M5.ugDS'
如果 shell 变量 $ACCOUNT 包含帐户名(alice、bob 等),并且 $YAML 是 YAML 文件的文件名,我可以使用 sed 添加一个新条目,如下所示。它将新帐户直接插入到“admin::common::passwords:”行下,所以我知道它位于正确的节中:
sed -i /'^admin::common::passwords:'/a"\ ${ACCOUNT}:\n password: '${ENCRYPTED_PASS}'" $YAML
我可以使用 awk 找到这样的现有帐户。再次,这会找到该节,然后找到该节中的帐户(可能可以在 awk 中完成这一切,但我对 awk 不够好,这将完成这项工作):
awk "BEGIN{RS=ORS="\n\n";FS=OFS="\n"}/admin::common::passwords:/" $YAML | grep -A1 "^[ ]*${ACCOUNT}:"
但要删除一行,我能想到的最好的办法是:
sed -i "/^ ${ACCOUNT}:/,+1d" $YAML
这将删除文件中任何地方的任何行“^ alice:”,无论它是在 admin::common::passwords 节中还是其他地方。
我无法对文件的内容做出任何假设,除了它确实包含一个 admin::common:passwords 节(如果没有,那么它将由我的脚本的另一部分创建;该位是简单的)。
所以,我的问题是:我怎样才能改进这个 sed 来说'搜索“^ ${ACCOUNT}:”在 admin::common::passwords 节中,然后删除该匹配行及其下面的行'?
(当然,不一定非要使用 sed;awk 甚至 Perl 可能更适合这项任务)
答案1
sed "
/^admin::common::passwords:$/,/^[^ ].*:$/ {
/^ $ACCOUNT:$/ {
N;d
}
}" < "$YAML"
也就是说,将删除节包含在admin::common...
与下一个之间匹配的部分中whatever-section:
。
请注意,.
用户名中常见的字符也是正则表达式运算符,因此john.doe
会匹配john.doe
,但也会匹配johnWdoe
例如。
admin::common::passwords
请注意,如果您有两个连续的部分并且要删除的帐户位于第二个部分,则上述方法将不起作用。
如果,正如奥修斯指出的,各节运行到具有相同或更少数量的前导空格字符的下一行,并且您的admin::common::passwords:
节可能有一些前导空格,那么可能是时候切换到另一种语言,例如awk
:
awk -v account="$ACCOUNT" '
match($0, /[^ ]/) && RSTART <= n {n = 0}
n && NF == 1 && $1 == account ":" {getline; next}
!n && /^ *admin::common::passwords:$/ {
match($0, /[^ ]/)
n = RSTART
}
{print}' < "$YAML"
答案2
使用预构建的库来解析整个文件会更安全(例如,像这样https://stackoverflow.com/questions/1773805/how-can-i-parse-a-yaml-file) 进行修改,然后重新保存修改后的文件。像这样对结构化文本文件进行一些盲目的文本修改只会导致出现问题。