YAML 快速上手

2024-06-17 09:28:44 浏览数 (2)

YAML(YAML Ain’t a Markup Language)是专门用来写配置文件的语言,简洁强大,相比于 JSON 和 XML,更加便于开发人员读写。

YAML 配置文件后缀为.yml 或 .yaml。

1.语法

YAML 的基本语法规则如下:

  • 数据结构采用键值对的形式 key: value。
  • 键冒号后面要加空格(一般为 1 个空格)。
  • 字母大小写敏感。
  • 使用缩进表示层级关系。
  • 缩进只允许使用空格,不允许使用 Tab 键。
  • 缩进空格数可以任意,只要相同层级的元素左侧对齐即可。
  • 字符串值一般不使用引号,必要时可使用。使用双引号表示字符串时,会转义字符串中的特殊字符(例如n)。使用单引号时不会转义字符串中的特殊字符。
  • 数组中的每个元素单独一行,并以 - 开头。或使用方括号,元素用逗号隔开。注意短横杆和逗号后面都要有空格。
  • 对象中的每个成员单独一行,使用键值对形式。或者使用大括号并用逗号分开。
  • 文档以三个连字符---表示开始,以三个点号...表示结束,二者都是可选的。
  • 文档前面可能会有指令,在这种情况下,需要使用---来表示指令的结束。指令是一个%后跟一个标识符和一些形参。
  • 目前只有两个指令:%YAML指定文档的 YAML 版本,%TAG用于 tag 简写。二者都很少使用。
  • #表示注释,从这个字符一直到行尾,都会被解析器忽略。

2.历史版本

版本

发布日期

YAML 1.0

29 January 2004

YAML 1.1

18 January 2005

YAML 1.2.0

21 July 2009

YAML 1.2.1

1 October 2009

YAML 1.2.2

1 October 2021

3.数据结构

YAML 支持的数据结构有三种:

  • 对象:键值对的集合,又称为映射(mapping)、散列(hashes)、字典(dictionary)。
  • 数组:一组按次序排列的值,又称为序列(sequence)、列表(list)。
  • 标量:单个不可再分的值

下面分别介绍这三种数据结构。

对象

对象的一组键值对,使用冒号结构表示。

代码语言:javascript复制
name: Steve

YAML 也允许另一种写法,将所有键值对写成一个行内对象。

代码语言:javascript复制
who: { name: Steve, age: 18 }

当然,如果对象元素太多一行放不下,那么可以换行。

代码语言:javascript复制
who:
  name: Steve
  age: 18

数组

一组以连字符开头的行,构成一个数组。注意,连字符后需添加空格。

代码语言:javascript复制
animals:
  - Cat
  - Dog
  - Goldfish

连字符前可以没有缩进,也就是说下面这种写法也是 OK 的,但是还是建议缩进,因为更加易读。

代码语言:javascript复制
animals:
- Cat
- Dog
- Goldfish

数组也可以采用行内表示法。

代码语言:javascript复制
animal: [Cat,Dog,Goldfish]

如果数组元素是一个数组,则可以在连字符下面再缩进输入一个数组。

代码语言:javascript复制
animals:
  -
    - Cat
    - Dog
  -
    - Fish
    - Goldfish

如果是行内表示,则为:

代码语言:javascript复制
animals: [[Cat,Dog],[Fish,Goldfish]]

如果数组元素是一个对象,可以写作:

代码语言:javascript复制
animals:
  - species: dog
    name: foo
  - species: cat
    name: bar

对应的 JSON 为:

代码语言:javascript复制
{
    "animals": [
        {
            "species": "dog",
            "name": "foo"
        },
        {
            "species": "cat",
            "name": "bar"
        }
    ]
}

复合结构

对象和数组可以结合使用,形成复合结构。

代码语言:javascript复制
languages:
 - Ruby
 - Perl
 - Python 
websites:
 YAML: yaml.org 
 Ruby: ruby-lang.org 
 Python: python.org 
 Perl: use.perl.org 

对应的 JSON 表示如下:

代码语言:javascript复制
{
  "languages": [
    "Ruby",
    "Perl",
    "Python"
  ],
  "websites": {
    "YAML": "yaml.org",
    "Ruby": "ruby-lang.org",
    "Python": "python.org",
    "Perl": "use.perl.org"
  }
}

标量

标量是最基本、不可再分的值。有以下 7 种:

  • 字符串
  • 布尔值
  • 整数
  • 浮点数
  • Null
  • 时间
  • 日期

使用一个例子来快速了解标量的基本使用:

代码语言:javascript复制
boolean: 
    - TRUE	# true、True 都可以
    - FALSE	# false、False 都可以
float:
    - 3.14			# 数值直接以字面量的形式表示
    - 6.8523015e 5	# 可以使用科学计数法
int:
    - 123							# 数值直接以字面量的形式表示
    - 0b1010_0111_0100_1010_1110	# 二进制表示
null:
    nodeName: 'node'
    parent: ~  		 # 使用~表示 null
string:
    - hello			 # 字符串默认不使用引号
    - "Hello world"  # 使用双引号或单引号包裹含有空格或特殊字符(如冒号)的字符串
    - newline
      newline1		 # 字符串可以拆成多行,每一换行符会被转化成一个空格
date:
    - 2018-02-17     # 日期必须使用 ISO 8601 格式,即 yyyy-MM-dd
datetime:
    - 2018-02-17T15:02:31 08:00    # 时间使用 ISO 8601 格式,时间和日期之间使用 T 连接, 08:00 表示时区

YAML 字符串有三种表示方式:

  • 无引号
  • 双引号
  • 单引号

字符串默认不需要引号,但是如果字符串包含空格或特殊字符(如冒号),需要加引号。

双引号字符串允许在字符串中使用转义序列来表示特殊字符,例如 n 表示换行,t 表示制表符,以及 " 表示双引号。

单引号字符串被视为纯粹的字面字符串,不支持转义序列。

如果字符串含有单引号,可以使用双引号包裹,反之亦然。

4.引用

锚点 & 和别名 *,可以用来完成引用。

代码语言:javascript复制
defaults: &defaults
  adapter:  postgres
  host:     localhost

development:
  database: myapp_development
  <<: *defaults

等同于下面的配置。

代码语言:javascript复制
defaults:
  adapter:  postgres
  host:     localhost

development:
  database: myapp_development
  adapter:  postgres
  host:     localhost

& 用来建立锚点(defaults),<< 表示合并到当前数据,* 用来引用锚点。

5.文本块

如果想引入多行的文本块,可以使用 || |->> >-

  • |

当内容换行时,保留换行符。

如果最后一行有多个换行符,只保留一个换行符。

代码语言:javascript复制
linefeed1: |
  some
  text
linefeed2: |
  some
  text
  
linefeed3: |
  some

  text

对应的 JSON 为:

代码语言:javascript复制
{
    "linefeed1": "somentextn",
    "linefeed2": "somentextn",
    "linefeed3": "somenntextn"
}
  • |

当内容换行时,保留换行符。

与 | 的区别是,如果最后一行有多个换行符,则保留实际数目。

代码语言:javascript复制
linefeed1: | 
  some
  text
linefeed2: | 
  some
  text
  
linefeed3: | 
  some

  text

对应的 JSON 为:

代码语言:javascript复制
{
    "linefeed1": "somentextn",
    "linefeed2": "somentextnn",
    "linefeed3": "somenntextn"
}
  • |-

当内容换行时,保留换行符,但最后的换行符不保留。

代码语言:javascript复制
linefeed1: |-
  some
  text
linefeed2: |-
  some
  text
  
linefeed3: |-
  some

  text

对应的 JSON 为:

代码语言:javascript复制
{
    "linefeed1": "somentext",
    "linefeed2": "somentext",
    "linefeed3": "somenntext"
}
  • >

当内容换行时,替换为空格,但保留最后一行的换行符。

如果最后一行有多个换行符,只保留一个换行符。

代码语言:javascript复制
linefeed1: >
  some
  text
linefeed2: >
  some
  text
  
linefeed3: >
  some

  text

对应的 JSON 为:

代码语言:javascript复制
{
    "linefeed1": "some textn",
    "linefeed2": "some textn",
    "linefeed3": "somentextn"
}
  • >

当内容换行时,替换为空格,但保留最后一行的换行符。

与 > 的区别是,如果最后一行有多个换行符,则保留实际数目。

代码语言:javascript复制
linefeed1: > 
  some
  text
linefeed2: > 
  some
  text
  
linefeed3: > 
  some

  text

对应的 JSON 为:

代码语言:javascript复制
{
    "linefeed1": "some textn",
    "linefeed2": "some textnn",
    "linefeed3": "somentextn"
}
  • >-(缺省行为)

当内容换行时,替换为空格,不保留最后一行的换行符。

代码语言:javascript复制
linefeed1: >-
  some
  text
linefeed2: >-
  some
  text
  
linefeed3: >-
  some

  text

对应的 JSON 为:

代码语言:javascript复制
{
    "linefeed1": "some text",
    "linefeed2": "some text",
    "linefeed3": "somentext"
}

注意:以上 6 个特殊字符,|->- 用得最多。

6.显示指定类型

有时需要显示指定某些值的类型,可以使用 !(感叹号)显式指定类型。

! 单叹号通常是自定义类型,!! 双叹号是内置类型。

代码语言:javascript复制
# !!str 指定为字符串
string.value: !!str HelloWorld!
# !!timestamp 指定为日期时间类型
datetime.value: !!timestamp 2021-04-13T02:31:00 08:00

内置的类型如下:

代码语言:javascript复制
!!int:整数类型
!!float:浮点类型
!!bool:布尔类型
!!str:字符串类型
!!binary:二进制类型
!!timestamp:日期时间类型
!!null:空值
!!set:集合类型
!!omap,!!pairs:键值列表或对象列表
!!seq:序列
!!map:散列表类型

7.单文件多文档

一个 yaml 文件可以包含多个 yaml 文档,使用三个连字符---分隔。

代码语言:javascript复制
a: 10
b: 
	- 1
	- 2
	- 3
---
a: 20
b: 
	- 4
	- 5 

这种情况在 K8S 和 SpringBoot 中非常常见。

比如 SpringBoot 在一个 application.yml 文件中,通过 — 分隔多个不同配置,根据 spring.profiles.active 的值来决定启用哪个配置。

代码语言:javascript复制
# 公共配置
spring:
  profiles:
    active: prod #使用名为 prod 的配置,这里可以切换成 dev。
  datasource:
    url: jdbc:mysql://localhost:3306/test_db?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
    password: 123456
    username: root
---
# 开发环境配置
spring:
  profiles: dev #profiles属性代表配置的名称。

server:
  port: 8080
---
# 生产环境配置
spring:
  profiles: prod

server:
  port: 80

8.解析

下面以 YAML 表示一个简单的后台服务配置:

代码语言:javascript复制
name: UserProfileServer
maxconns: 1000
queuecap: 10000
queuetimeout: 300
loginfo:
  loglevel: ERROR
  logsize: 10M
  lognum: 10
  logpath: /usr/local/app/log

下面以 Go 语言为例,解析上面的 YAML 配置。

因为 Go 官方并没有提供解析 YAML 的标准库,所以这里基于第三方开源库 go-yaml 来完成对 YAML 文件的解析。

第一步,将 YAML 配置文件的内容在 Convert YAML to Go struct 转换为 Go struct。

代码语言:javascript复制
type Server struct {
	Name         string `yaml:"name"`
	Maxconns     int    `yaml:"maxconns"`
	Queuecap     int    `yaml:"queuecap"`
	Queuetimeout int    `yaml:"queuetimeout"`
	Loginfo      struct {
		Loglevel string `yaml:"loglevel"`
		Logsize  string `yaml:"logsize"`
		Lognum   int    `yaml:"lognum"`
		Logpath  string `yaml:"logpath"`
	} `yaml:"loginfo"`
}

第二步,利用第三方开源库 go-yaml 来完成对 YAML 文件的解析。

代码语言:javascript复制
package main

import(
	"io/ioutil"
	"fmt"
	"os"	

	yaml "gopkg.in/yaml.v3"
)

func main() {
  file, err := os.Open("server.yaml")
  if err != nil {
    fmt.Printf("error:%vn", err)
    return
  }
  defer file.Close()

  data, err := ioutil.ReadAll(file)
  if err != nil {
    fmt.Printf("error:%vn", err)
    return
  }
  v := Server{}
  err = yaml.Unmarshal(data, &v)
  if err != nil {
    fmt.Printf("error:%vn", err)
    return
  }
  fmt.Printf("% vn", v)
}

运行输出:

代码语言:javascript复制
{Name:UserProfileServer Maxconns:1000 Queuecap:10000 Queuetimeout:300 Loginfo:{Loglevel:ERROR Logsize:10M Lognum:10 Logpath:/usr/local/app/log}}

9.完整示例

代码语言:javascript复制
%YAML 1.2
---
receipt:     Oz-Ware Purchase Invoice
date:        2012-08-06
customer:
    given:   Dorothy
    family:  Gale
   
items:
    - part_no:   A4786
      descrip:   Water Bucket (Filled)
      price:     1.47
      quantity:  4

    - part_no:   E1628
      descrip:   High Heeled "Ruby" Slippers
      size:      8
      price:     133.7
      quantity:  1

bill-to:  &id001
    street: | 
            123 Tornado Alley
            Suite 16
    city:   East Centerville
    state:  KS

ship-to:  *id001   

specialDelivery:  >
    Follow the Yellow Brick
    Road to the Emerald City.
    Pay no attention to the
    man behind the curtain.
...

注意在 YAML 中,字符串不一定要用双引号标示。另外,在缩进中空白字符的数目并不是非常重要,只要相同层次结构的元素左侧对齐就可以了(不过不能使用 TAB 字符)。

%YAML 1.2 表示版本。

这个文件的顶层由七个键值组成:其中一个键值"items",是两个元素构成的数组(或称清单),这数组中的两个元素同时也是包含了四个键值的散列表。

文件中重复的部分用这个方法处理:使用锚点(&)和引用(*)标签将"bill-to"散列表的内容复制到"ship-to"散列表。也可以在文件中加入选择性的空行,以增加可读性。

在一个文件中,可同时包含多个文件,并用---分隔。选择性的符号...可以用来表示文件结尾(在流通信中,这非常有用,可以在不关闭流的情况下,发送结束信号)。


参考文献

The Official YAML Web Site Convert YAML to JSON go-yaml - Github Convert YAML to Go struct YAML 详解与实战-CSDN YAML - 维基百科,自由的百科全书 Brief YAML reference — Camel 0.1.2 documentation

0 人点赞