YAML:在YAML中,字符串是否需要加引号?

760

我正在尝试为Rails项目编写一个国际化的YAML字典。但我有些困惑,因为在一些文件中,我看到了用双引号括起来的字符串,而在另一些文件中则没有。请考虑以下几点:

  • 示例1 - 所有字符串都使用双引号;
  • 示例2 - 除了最后两个字符串外,没有任何字符串使用引号;
  • YAML食谱中说:用双引号括起来的字符串允许您使用转义序列来表示ASCII和Unicode字符。这是否意味着只有在要转义某些字符时才需要使用双引号?如果是这样,为什么第一个示例中他们无论何时都使用双引号呢?是为了统一性或风格上的原因吗?
  • 示例2的最后两行使用了! - 非特定标记,而第一个示例的最后两行则没有 - 但它们都可以工作。

我的问题是:在YAML中使用不同类型的引号的规则是什么?

是否可以说:

  • 通常情况下,您不需要引用;
  • 如果要转义字符,请使用双引号;
  • 在单引号内使用!,何时使用...?!?

6
第二个链接已经失效,我建议将你的示例放到问题中。 - heroin
8个回答

1009

在翻阅了引用在问题中的YAML cookbook并进行了一些测试后,这是我的解释:

  • 通常情况下,你不需要使用引号。
  • 如果你的键或值为10但你想返回一个字符串而不是一个Fixnum,则使用引号来强制将其作为字符串处理,比如写'10'"10"
  • 如果你的值包含特殊字符(例如:{}[],&*#?|-<>=!%@\),则使用引号。
  • 单引号可以让你在字符串中放置几乎任何字符,并且不会尝试解析转义代码。 '\n' 将被返回为字符串\n
  • 双引号解析转义字符。 "\n"将作为换行符返回。
  • 感叹号引入一个方法,比如!ruby/sym会返回一个Ruby符号。

对我来说,最佳方法似乎是除非必须使用引号,否则不要使用引号,并且除非你特别想处理转义代码,否则使用单引号。

更新

"Yes" 和 "No" 应该用引号(单引号或双引号)括起来,否则它们将被解释为TrueClass和FalseClass值:

en:
  yesno:
    'yes': 'Yes'
    'no': 'No'

34
这并不是完整的情况。例如,在纯字符串中除了开头位置以外,可以随意使用 @ 和 `,因为它们是保留指示符 - Adam Spiers
37
我并不打算提供完整的情况,只是提供一些经验法则。是的,看起来有时候可以不用引号使用一些特殊字符(保留标识符),但只要保留标识符不开始普通标量,使用引号也不是错误的。请注意,不要改变原意。 - Mark Berry
52
YAML中的字符串规则非常复杂,因为有许多不同类型的字符串。我在这里写了一个表格:https://dev59.com/0G865IYBdhLWcg3wfemU#21699210 - Steve Bennett
121
鉴于所有这些警告,我宁愿到处使用引号 :-/ - Vicky Chijwani
7
这里是我写的一个相当完整的参考文献: http://blogs.perl.org/users/tinita/2018/03/strings-in-yaml---to-quote-or-not-to-quote.html - tinita
显示剩余5条评论

91

虽然Mark的答案很好地总结了YAML语言规则下何时需要使用引号,但我认为许多开发人员/管理员在处理YAML字符串时会问自己“我的处理字符串的经验法则应该是什么?”

这听起来有些主观,但如果你想只在真正需要的情况下使用引号,按照语言规范记住的规则数量有点过多,对于指定最常见数据类型这样简单的事。别误解我的意思,当你经常使用YAML时,你最终会记住它们,但如果你偶尔使用它而且没有养成编写YAML的习惯,你真的想花时间记住所有规则才能正确指定字符串吗?

“经验法则”的整个目的是节省认知资源并处理常见任务,而无需思考。我们的“CPU”时间可以用于比正确处理字符串更有用的事情。从这个纯实际的角度出发,我认为最好的经验法则是用单引号括起字符串。其背后的原理是:

  • 除非你需要使用转义序列,否则单引号字符串适用于所有情况。
  • 在单引号字符串中,你唯一需要处理的特殊字符就是单引号本身。

对于偶尔使用 YAML 的用户来说,只需记住这两个规则,可以最大程度地减少认知负担。


1
我喜欢这个答案。我认为YAML的整个重点是保持简单。然而,我在寻找答案,为什么我的最新YAML中sizeInBytes: 12345678的int值必须被“引用”,因为显然有些东西想要一个字符串配置属性(可能?) - 但实际上我仍然不知道答案。 - Jordan Gee
4
一个更简单的方法是:对于字符串使用双引号。 - acmoune
2
@acmoune 我不确定双引号是否是最好的“默认”选项,因为它们自动解释转义序列的方式可能不符合您的预期。但是,如果您始终记得这一点,并且如果您特别需要它,那么为什么不呢? - wombatonfire

69

这个问题已经有了一些很好的回答。然而,我想扩展它们并从new official YAML v1.2.2 specification(于2021年10月1日发布)提供一些背景信息,这是关于YAML的所有事情的“真实来源”。

有三种不同的样式可以用来表示字符串,每种样式都有自己的优缺点:

YAML提供了三种流量标量样式:双引号、单引号和纯文本(未加引号)。每种样式在可读性和表现力之间提供了不同的权衡。

双引号样式:

  • 双引号样式由双引号包围的指示符指定。这是唯一能够通过使用\转义序列来表示任意字符串的样式。这是以需要转义\"字符的代价为代价的。

单引号样式:

  • 单引号样式由单引号包围的指示符指定。因此,在单引号标量中,需要重复使用这些字符。这是单引号标量执行的唯一形式的转义。特别地,\ "字符可以自由使用。这限制了单引号标量只能使用可打印的字符。此外,只有在一个空格被非空格字符包围时才可能在长的单引号行上断开。

纯文本(未加引号)样式:

  • 纯文本(未加引号)样式没有识别指示符并且不提供任何形式的转义。因此,它是最可读,最受限制,最上下文敏感的样式。除了限制的字符集之外,纯量标量不能是空的,也不能包含前导或尾随的空格字符。只有在一个空格被非空格字符包围时才可能在长的纯量行上中断。 纯量标量不能以大多数指示符开头,因为这会与其他YAML结构造成歧义。但是,如果后面跟着一个非空格的“safe”字符,则可以使用-指示符作为第一个字符,因为这不会引起歧义。

TL;DR

总之,根据官方的YAML规范,应该:

  • 如果适用,使用未引用的样式,因为它是最易读的。
  • 如果字符串中包含"\等字符需要避免转义以提高可读性,则使用单引号样式(')。
  • 当前两个选项不足以满足需求时,例如需要更复杂的换行或需要非可打印字符时,请使用双引号样式(")。

1
感谢您的总结。它介绍了如何划分空格,这是我在回答中没有考虑到的。但它遗漏了引号的一个主要决定因素:是否要强制数据类型为字符串,而默认情况下是其他类型。这在第2.4节中简要介绍:“在YAML中,未标记的节点根据应用程序赋予一种类型。”最简单的示例2.21显示string:'012345'。该部分还涵盖了更复杂的显式类型转换,这是我之前不知道的! - Mark Berry
一些额外的注意事项:普通标量绝不能包含:#字符组合。此外,在流集合内或用作隐式键时,普通标量不能包含[]{},字符。 - undefined
我不明白的是:根据规范,为什么允许在纯文本开头使用&呢?这样不是会把它当作引用吗? - undefined

23

在yaml中,只有当值的开头可能被误解为数据类型或者值包含一个 ":" (因为它可能被误解为键)时,字符串才需要引号。

例如:

foo: '{{ bar }}'

需要加引号,因为它可能被误解为数据类型dict,但是

foo: barbaz{{ bam }}

由于它不以关键字符开始,因此不会执行。接下来,

foo: '123'

需要加引号,因为它可能会被误解为数据类型int,但是

foo: bar1baz234
bar: 123baz

不能被误解为int,因此不会发生。

foo: 'yes'

需要加引号,因为它可能被误解为数据类型 bool

foo: "bar:baz:bam"

由于该值可能被误解为键,因此需要用引号引起来。

这只是示例。使用yamllint可以避免以错误的标记开始值。

foo@bar:/tmp$ yamllint test.yaml 
test.yaml
  3:4       error    syntax error: found character '@' that cannot start any token (syntax)

如果想要高效地使用yaml,这是必不可少的。

像一些人建议的那样引用所有字符串,就像在Python中使用括号一样。这是一种不好的习惯,会影响可读性,并放弃掉不需要引用字符串的美好特性。


2
感谢提供的示例。看起来我们是同意的;正如我在我的答案中所说:“最好的方法是除非必须,否则不要使用引号。”关于您有用的数据类型规则的问题:您是否特别指的是Ruby on Rails中的YAML,就像OP的问题一样?似乎数据类型的解释可能因编程语言而异。 - Mark Berry
1
@MarkBerry 感谢您的建议。对我来说,一般规则也是:在必要时再引用。而且,您正确地观察到,我使用了 Python 的示例而不是 Ruby 的示例。我是有意为之的。为了突出抽象信息:1)使用 linter 2)Yaml 不限于一种语言,但确实是一种语言。这就是为什么我使用“键:值”术语的原因。 - user657127

3
我在使用 Docker 开发 Rails 应用时有这个疑虑。
我通常的做法是不要使用引号,包括以下情况:
- 变量,如 ${RAILS_ENV} - 冒号(:)分隔的值,如 postgres-log:/var/log/postgresql - 其他字符串
但对于需要转换为字符串的整数值,我会使用双引号,例如:
- docker-compose 版本,如 version: "3.8" - 端口号,如 "8080:8080" - 镜像,如 "traefik:v2.2.1" 但对于布尔值、浮点数、整数和其他情况,如果使用双引号可能会被解释为字符串,请不要使用双引号。
以下是一个示例的docker-compose.yml文件,以解释这个概念:
version: "3"

services:
  traefik:
    image: "traefik:v2.2.1"
    command:
      - --api.insecure=true # Don't do that in production
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
    ports:
      - "80:80"
      - "8080:8080"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro

That's all.

I hope this helps


4
违反 - 如果您的值包含“:”,请在另一个答案中使用引号。 - Smart Manoj

0

0
这是一个小函数(未针对性能进行优化),如果需要,它会用单引号引用您的字符串,并测试结果是否可以解组回原始值:https://go.dev/play/p/AKBzDpVz9hk。它不是通过测试规则来实现的,而是直接使用了编组器本身,并检查编组和解组后的值是否与原始版本匹配。
func yamlQuote(value string) string {
    input := fmt.Sprintf("key: %s", value)

    var res struct {
        Value string `yaml:"key"`
    }

    if err := yaml.Unmarshal([]byte(input), &res); err != nil || value != res.Value {
        quoted := strings.ReplaceAll(value, `'`, `''`)
        return fmt.Sprintf("'%s'", quoted)
    }

    return value
}

-1
version: "3.9"

services:
  seunggabi:
    image: seunggabi:v1.0.0
    command:
      api:
        insecure: true
    ports:
      - 80:80
      - 8080:8080
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro

docker compoese up docker-compose.yaml

如果您使用的是docker compose v2,则不需要为布尔值使用引号。
只有版本需要引号。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接