python yaml 包在不需要时解析新行

python yaml package parsing new line when not needed

我已经搜索了几天,试图找出为什么我的 yaml 解析器(使用 PyYaml)没有像原始状态那样保存回 YAML。

YAML 中的原始行是:

healthcheck:
  test: ["CMD-SHELL", "[ x\"`curl -k --silent -w '%{http_code}' https://localhost:4433 | grep 401`\" = x\"\" ] && exit 1 || exit 0"]       
  interval: 30s

但是新行(只是加载文件并再次保存):

    healthcheck:
      interval: 30s
      test:
      - CMD-SHELL
      - '[ x"`curl -k --silent -w ''%{http_code}'' https://localhost:4433 | grep 401`"
      = x"" ] && exit 1 || exit 0'

这里有两个问题: 1) "test" 值变成一个列表而不是 1 行键值对。 2)这里实际上有3个换行

a) -CMD-SHELL 
b)- '[ x"`curl -k --silent -w ''%{http_code}'' https://localhost:4433 | grep 401`"
c)= x"" ] && exit 1 || exit 0'

那么另一个问题是,为什么第三行是从第二行断的? (如果我显示白色 space,你会看到在第二行的末尾有 LF 然后开始第三行

PyYAML 不太擅长在往返过程中保留样式(加载、修改、安全),它实际上无法通过使用其 load/dump 参数按原样保留您的输入。为此,您需要修改 PyYAML 解析器。

这就是 ruamel.yaml 所做的(免责声明:我是该包的作者),它是专门为支持此类程序化往返(包括保留评论)而开发的:

import sys
import ruamel.yaml
from pathlib import Path

yaml_file = Path('test.yaml')
out_file = Path('out.yaml')

yaml = ruamel.yaml.YAML()
yaml.width = 2048
yaml.preserve_quotes = True
data = yaml.load(yaml_file)
yaml.dump(data, out_file)

这为您提供了一个 out.yaml,其内容与 test.yaml.

完全相同

默认宽度 (80) 会像在 PyYAML 中那样换行,因此将其设置为比最大长度行更长的值。 preserve_quotes 是必需的,否则 "CMD-SHELL" 中多余的引号会被删除。

上面假设Python3(对于pathlib),如果你还是运行Python2你可以交一个正常打开的普通文件句柄:

with open('test.yaml') as fp:
    data = yaml.load(fp)
with open('out.yaml', 'w') as fp:
    yaml.dump(data, fp)

我想你可能对 YAML 语法有一些误解。这个:

test: ["this", "is", "a", "list"]

完全等同于:

test:
  - this
  - is
  - a
  - list

还有这个:

- "This is a string value"

完全等同于:

- "This is a
  string value"

如果我将您的示例放入文件 data.yml:

$ cat data.yml
healthcheck:
  test: ["CMD-SHELL", "[ x\"`curl -k --silent -w '%{http_code}' https://localhost:4433 | grep 401`\" = x\"\" ] && exit 1 || exit 0"]
  interval: 30s

然后用PyYAML解析:

>>> import yaml
>>> with open('data.yml') as fd:
...   data = yaml.load(fd)
... 

我得到以下 Python 数据结构:

>>> pprint.pprint(data)
{'healthcheck': {'interval': '30s',
                 'test': ['CMD-SHELL',
                          '[ x"`curl -k --silent -w \'%{http_code}\' https://localhost:4433 | grep 401`" = x"" ] && exit 1 || exit 0']}}

如果我使用 PyYAML 转储它,我会得到:

>>> print yaml.dump(data)
healthcheck:
  interval: 30s
  test: [CMD-SHELL, '[ x"`curl -k --silent -w ''%{http_code}'' https://localhost:4433
      | grep 401`" = x"" ] && exit 1 || exit 0']

...这似乎很好。我可以请求更详细的列表语法,在这种情况下,我会得到你在示例中显示的内容:

>>> print yaml.dump(data, default_flow_style=False)
healthcheck:
  interval: 30s
  test:
  - CMD-SHELL
  - '[ x"`curl -k --silent -w ''%{http_code}'' https://localhost:4433 | grep 401`"
    = x"" ] && exit 1 || exit 0'

...这将解析为与原始文档完全相同的 Python 数据结构。除"looking different"外,实际数据相同。