【问题标题】:python yaml package parsing new line when not neededpython yaml包在不需要时解析新行
【发布时间】:2026-02-13 13:40:01
【问题描述】:

我现在正在寻找几天,试图找出为什么我的 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)“测试”值成为一个列表而不是 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'

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

【问题讨论】:

    标签: python parsing yaml pyyaml


    【解决方案1】:

    我想你可能对 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 数据结构。除了“看起来不同”之外,实际数据是相同的。

    【讨论】:

      【解决方案2】:

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

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

      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)
      

      这会为您提供与test.yaml 完全相同内容的out.yaml

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

      以上假设 Python 3(用于 pathlib),如果您仍然运行 Python 2,您可以提交正确打开的普通文件句柄:

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

      【讨论】: