エンジニア成長日記

成長するにはインプットばかりでなく、アウトプットも必要である。日々の成長記録を書き留めていきたい。

YAMLを覚えよう

はじめに

最近、ansibelなどの設定書式で用いられる記法「YAML」について、紹介します。 YAMLは、箇条書きのように記載できるため、大変わかりやすいフォーマットです。

YAMLとは?

概要

YAML Ain’t Markup Languageの略で、構造化データの表現する記法になります。 主に以下のような用途で利用されます。

  • 設定ファイル
  • データ保存
  • データ交換

XMLとの違い

XMLYAML大きな違いは、表記方法が異なります。

XML = 開始、終了タグ()を利用した構造化データを表現。 YAML = タグの代わりに、インデントを利用した構造化データを表現。

XMLと比べ、人が見る場合に非常にわかりやすい構造で表現することが可能となります。

以下、XMLYAMLを比較すために、サンプルを記載します。

例)  書籍の一覧を表現する場合

[XML]

<books>
<book>
    <name>書籍タイトルその1</name>
    <author>山本 太郎</author>
</book>
<book>
    <name>書籍タイトルその2</name>
    <author>鈴木 一郎</author>
</book>
</books>

[YAML]

books:
- name:書籍タイトルその1
    author:山本 太郎
- name:書籍タイトルその2
    author:鈴木 一郎

公式サイト

YAMLの公式サイトは、以下になります。書式の仕様などを調べる際にお役立てください。

URL http://yaml.org

環境

今回は、YAMLを構文を確認するため、Pythonを利用します。 各バージョンは、以下のとおりです。

□ OS

[root@yaml  ~]# cat /etc/redhat-release
CentOS release 6.6 (Final)

YAML

[root@yaml  ~]# rpm -aq |grep yaml
libyaml-0.1.6-1.el6.x86_64
libyaml-devel-0.1.6-1.el6.i686

Python

[root@yaml  ~]# python -V
Python 2.6.6

※補足

今回は、YAMLファイルとPythonを繋ぐライブラリはAnsibleで利用されている「LibYAML for Python」を使います。

項目 説明
ライブラリ名 LibYAML for Python
WEBサイト http://pyyaml.org/wiki/PyYAML
ドキュメント http://pyyaml.org/wiki/LibYAML
ダウンロード http://pyyaml.org/download/libyaml
バージョン 0.1.6
YAML対応 YAML 1.1準拠

YAMLを触ってみる。

まず、YAMLの構文に慣れてもらうため、簡単なプログラムを書いてみます。

MyYaml.ymlの作成

YAMLを記述するファイルを作成します。 拡張子は、「yml」になります。

ここでは、配列を表す記述を記載してみます。

[root@yaml  ~]# vi MyYaml.yml
[root@yaml  ~]# cat MyYaml.yml
- a
- b
- c

parse.pyの作成

記載したYAMLファイルの構文をチェックするため、Pythonのコードを記述するファイルを作成します。 拡張子は、「py」になります。 ※parseとは、構文に従って分析する、品詞を記述する、構文解析するなどの意味を持つ単語です。

[root@yaml  ~]# vi parse.py
[root@yaml  ~]# cat parse.py
#-*- coding: utf-8 -*-
# yamlを利用するための宣言
import yaml;

# MyYaml.ymlファイルを読み込み(パース)後、結果を標準出力。
print yaml.load(open('MyYaml.yml').read())

動作確認

作成したMyYaml.ymlを、parse.pyでパースして、出力結果を確認します。

[root@yaml  ~]# python parse.py
['a', 'b', 'c']

以降の章からは、「MyYaml.yml」へ記載内容と「python parse.py」を実行した結果のみを記述して、YAMLファイルの書き方を覚えていきます。

XXXXXXX            # MyYaml.ymlの編集内容記載

(結果)
[XXXXXXX]          # python parse.pyを実行した結果記載

基本的な構文を抑えよう。

YAML では、主に次の 3 つの組み合わせでデータを表現します。

  • シーケンス(Sequence) データを連続的に並べた構造を表す。 Python=配列/Java=リストと呼ばれる。

  • マッピング(Mapping) キーと値のペアを列挙する構造を表す。 Python=ハッシュ/Java=マップと呼れる。

  • スカラー(scalar) 文字列、数値、真偽値などの変数(値の入れ物)を表す。 YAMLで記述した値は、自動的にデータ型を判別する。 ※Javaのようなデータ型の宣言は必要なく、中間のライブラリ(JYaml)が自動的に判別し、値を渡してくれます。

ここでは、上記の3つの書き方を紹介します。

シーケンス(Sequence)

□ 基本書式

- d1
- d2
- d3

(結果)
['d1', 'd2', 'd3']

☆注意点☆

  • [-]の後の半角スペースは必須。
- d1
-d2    #半角スペースなし
- d3

(結果)
Traceback (most recent call last):
File "parse.py", line 6, in 
~ 略 ~

□ 入れ子(ネスト)書式

- d1
- 
    - sd2-1
    - sb2-2
- d3

(結果)
['d1', ['d2-1', 'd2-2'], 'd3']

☆注意点☆

  • 字下げにタブ・全角空白は使えない。※半角スペース2つで記述。
- d1
- 
- sd2-1 # 字下げにタブ使用。
    - sb2-2
- d3

(結果)※エラーが発生。
Traceback (most recent call last):
File "parse.py", line 6, in 
~ 略 ~

  • 入れ子の際、親の値は記載しない。
- d1
- d2  # 親値を記載
    - sd2-1
    - sb2-2
- d3

(結果) ※入れ子にならず、1つ配列として認識される。
['d1', 'd2 - sd2-1 - sd2-2', 'd3']

マッピング(Mapping)

□ 基本書式

key1: val1
key2: val2

(結果)
{'key2': 'val2', 'key1': 'val1'}

☆注意点☆

  • :(コロン)の後の半角スペースは必須(1つ以上)。
key1: val1
key2:val2   # 半角スペースなし

(結果)※エラーが発生。
Traceback (most recent call last):
File "parse.py", line 6, in 
~ 略 ~

  • key:の「:(コロン)」の前に半角スペースを入れることも可。 ** インデントを揃えることが出来る。
hostname: nsw1.exzample.com 
ipv4    : 192.168.0.1  # ipv4の後に半角スペースを入れる

(結果)
{'hostname': 'nsw1.exzample.com', 'ipv4': '192.168.0.1'}

□ 入れ子(ネスト)書式

keys:
skey1: val1
skey2: val2

(結果)
{'keys': {'skey1': 'val1', 'skey2': 'val2'}}

☆注意点☆

  • 字下げにタブは使えない。半角スペース2つで記述。
keys:
skey1: val1
skey2: val2  #字下げにタブ使用。

(結果)※エラーが発生。
Traceback (most recent call last):
File "parse.py", line 6, in 
~ 略 ~

  • 入れ子の際、親の値は記載しない。
keys: vals        # 親値を記載
skey1: val1
skey2: val2
(結果)※エラーが発生。
Traceback (most recent call last):
File "parse.py", line 6, in 
~ 略 ~

スカラー(scalar)

□ 自動判別

  • 記載した値が自動的に判別されることを確認します。
# 文字列
str1: 文字列
str2: string

# 整数
decimal1:  123                           # 10 進数
decimal2:  1,234,567,890                 # 10 進数
octal:     0644                          # 8 進数
hexa:      0xFF                          # 16 進数

# 浮動小数点
float1:    0.05

# 真偽値 (true, yes, false, no)
bool1:     true                          # 真
bool2:     yes                           # 真
bool3:     on                            # 真
bool4:     false                         # 偽
bool5:     no                            # 偽
bool6:     off                           # 偽

# Null値 (null, ~)
null1:     ~
null2:     null

# 日付 (yyyy-mm-dd)
date:      2005-01-01

# タイムスタンプ (yyyy-mm-dd hh:mm:ss [+-]hh:mm)
stamp:     2005-01-01 00:00:00 +09:00

(結果)
{'date': datetime.date(2005, 1, 1), 'float1': 0.050000000000000003, 'bool5': False,
    'bool4': False, 'bool6': False, 'bool1': True, 'bool3': True, 'bool2': True,
    'hexa': 255, 'str2': 'string', 'str1': u'\u6587\u5b57\u5217', 'decimal2': '1,234,567,890',
    'stamp': datetime.datetime(2004, 12, 31, 15, 0), 'null1': None, 'octal': 420, 'null2': None, 'decimal1': 123}

□ 文字列変換

文字列として変換したい場合は、「"“」(ダブルコーテーション)で囲みます。

# 整数
decimal1:  123                           # 10 進数
decimal2:  1,234,567,890                 # 10 進数
octal:     0644                          # 8 進数
hexa:      0xFF                          # 16 進数

# 整数
decimal1:  "123"                           # 10 進数
decimal2:  "1,234,567,890"                 # 10 進数
octal:     "0644"                          # 8 進数
hexa:      "0xFF"                          # 16 進数

# 浮動小数点
float1:    "0.05"

# 真偽値 (true, yes, false, no)
bool1:     "true"                          # 真
bool2:     "yes"                           # 真
bool3:     "on"                            # 真
bool4:     "false"                         # 偽
bool5:     "no"                            # 偽
bool6:     "off"                           # 偽

# Null値 (null, ~)
null1:     "~"
null2:     "null"

# 日付 (yyyy-mm-dd)
date:      "2005-01-01"

# タイムスタンプ (yyyy-mm-dd hh:mm:ss [+-]hh:mm)
stamp:     "2005-01-01 00:00:00 +09:00"

(結果)
{'bool5': 'no', 'bool4': 'false', 'bool6': 'off', 'bool1': 'true',
    'bool3': 'on', 'bool2': 'yes', 'hexa': '0xFF', 'null1': '~', 'null2': 'null',
    'decimal2': '1,234,567,890', 'octal': '0644', 'date': '2005-01-01', 'float1': '0.05',
    'decimal1': '123', 'stamp': '2005-01-01 00:00:00 +09:00'}

    ※文字列変換前
    {'bool5': False, 'bool4': False, 'bool6': False, 'bool1': True,
    'bool3': True, 'bool2': True, 'hexa': 255, 'null1': None, 'null2': None,
    'decimal2': '1,234,567,890', 'octal': 420, 'date': datetime.date(2005, 1, 1),'float1': 0.050000000000000003,
    'decimal1': 123, 'stamp': datetime.datetime(2004, 12, 31, 15, 0)}

複雑な構文を抑えよう。

前章では、基本的な構文を紹介しました。 ここでは、シーケンスとマッピングを混合した書き方を紹介します。

マッピング - シーケンス

□ 基本書式

- bookname: sample1
    author: taro
- bookname: sample2
    author: jiro

(結果)
[{'bookname': 'sample1', 'author': 'taro'}, {'bookname': 'sample2', 'author': 'jiro'}]

シーケンス - マッピング

□ 基本書式

books:
    - sample1
    - sample2
authors:
    - taro
    - jiro

(結果)
{'books': ['sample1', 'sample2'], 'authors': ['taro', 'jiro']}

ブロックスタイルとフロースタイルを抑える。

YAMLの構文には、以下の2種類があります。

  • ブロックスタイル インデントを使って構造を表す書き方 1 行に収まる文字列の場合に便利。

  • フロースタイル 「{}」や「[]」を使って構造を表す書き方 複数行にわたる文字列の場合に便利。

今まで紹介した構造の書き方は、「 ブロックスタイル 」になります。 以降は、「 フロースタイル 」に構造の書換え、その違いを確認しましょう。

シーケンス(Sequence)

□ 基本書式

[d1, d2, d3]

(結果)
['d1', 'd2', 'd3']

☆注意点☆

  • [,]の後の半角スペースは必須。
[d1,d2, d3]  #d2の前の半角スペースなし

(結果)※エラーが発生。
Traceback (most recent call last):
File "parse.py", line 6, in 
~ 略 ~ 

□ 入れ子(ネスト)書式

ネストしたい個所を[]で囲みます。

[d1, [d2-1, d2-2], d3]

(結果)
['d1', ['d2-1', 'd2-2'], 'd3']

マッピング(Mapping)

□ 基本書式

{key1: val1, key2: val2}

(結果)
{'key2': 'val2', 'key1': 'val1'}

☆注意点☆

  • :,の後の半角スペースは必須。
{key1: val1,key2:val2} # key2前の半角スペースなし

(結果)※エラーが発生。
Traceback (most recent call last):
File "parse.py", line 6, in 
~ 略 ~ 

□ 入れ子(ネスト)書式

{keys: {skey1: val1, skey2: val2}}

(結果)
{'keys': {'skey1': 'val1', 'skey2': 'val2'}}

マッピング - シーケンス

□ 基本書式

[
    {bookname: sample1, author: taro},
    {bookname: sample2, author: jiro}
]

(結果)
[{'bookname': 'sample1', 'author': 'taro'}, {'bookname': 'sample2', 'author': 'jiro'}]

シーケンス - マッピング

□ 基本書式

{
    books: [sample1, sample2],
    authors: [taro, jiro]
}

(結果)
{'books': ['sample1', 'sample2'], 'authors': ['taro', 'jiro']}

改行を含むデータを扱ってみよう

YAMLの特性として改行すると次のデータになってしまいます。 改行を利用する場合は、下記の記号を利用します。

  • |(パイプ) ** 改行を保存することが可能。

  • (リダイレクト) ** 半角スペースに置き換える事が可能。

|(パイプ)を利用してみる。

key: |
    v
    a
    l

(結果)
{'key': 'v\na\nl\n'}    #改行コード「\n」が保存されます。

>(リダイレクト)を利用してみる。

key: >
    v
    a
    l

(結果)
{'key': 'v a l'}    #改行コードが半角スペースに変換されます。

まとめ

もう、お分かりの通り

%{color: red; font-size: 20px;}YAML% では、 %{color: red; font-size: 20px;}インデント%%{color: red; font-size: 20px;}重要% です。

途中でも紹介しましたが、YAMLファイルを作成する場合は、次の注意点は覚えておきましょう!!

  • 「-(ハイフン)」,「:(コロン)」,「,(カンマ)」の後には、1文字以上の半角スペースを入れること。
  • タブや全角スペースによるインデントは禁止。

おわりに…

今回は、Ansibleの構文を書くために必要な知識を覚えて頂くことを目的としたため、紹介しませんでしたが、他にもYAMLでは、次のようなことも出来ます。 ※Ptyhon(LibYAML for Python)で対応していない物もあり。

  • アンカー(&)とエイリアス() ** アンカー(&)で記載した構文を、エイリアス()で複製する。
  • …(ピリオド3つ) ** 指定した以降の処理は、スキップする。
  • —(ハイフン3つ) ** ドキュメントの終始を表す。

etc…

これらの情報は、以下のサイト(Ruby + YAML)がまとまっておりました。

http://magazine.rubyist.net/?0009-YAML

良ければ、ご参照ください。