DevKitLab Logo DevKitLab
YAML / 文字列 / パース / 設定ファイル

YAML の複数行文字列:| を使うときと > を使うとき

YAML の複数行の値には、ブロック記号が二つ、縦棒と大なり記号がある。さらに引用符あり・なしの書き方も行をまたげる。改行を保つか畳むかの規則はどれも違い、選び間違えるとスクリプトが一行につぶれる。落とし穴も含め、全体像をまとめる。

YAML で複数行の値を書きたい。シェルスクリプト、長い説明文、SQL の塊。YAML は二つの記号をくれる。|> だ。選び間違えても、エラーは出ない。値がそっと間違うだけだ。> で書いたスクリプトは一行につぶれ、動かない。| で書いた説明文は、画面に収めるためだけに入れた改行を全部残す。

この二つは、ふつう「リテラル」と「折り畳み」と呼ばれる。名前は正しいが、選ぶ瞬間には役立たない。本当に決着をつける問いはこうだ。この文章の改行に、意味はあるか。

意味があるなら、残す。| を使う。スクリプトの改行はコマンドを区切る。表の改行は行を区切る。意味がないなら——エディタに収めるために長い一文を折っただけなら——消す。> を使う。

これで大半は片づく。だがブロックスカラーには、まだ角がある。いくつかは噛みつく。この記事では一通り見ていく。二つの記号、末尾の改行、インデントの罠、そして——ブロックスカラーは複数行文字列を書く唯一の方法ではない、ということだ。

| は改行を保ち、> は改行をスペースに畳む

同じ入力、記号は二つ、結果は二つの別の文字列。| なら:

message: |
  line one
  line two

"line one\nline two\n" になる。改行は残る。> なら:

message: >
  line one
  line two

"line one line two\n" になる。改行が一つのスペースになった。まるで最初から一行で書いたようだ。

このスペースこそ > の用途だ。長い文章のためにある。長い説明をファイル内で折り返して読みやすくするが、最終的には一行で届けたい。改行が構造の一部である場所では、まったくの誤りだ。スクリプトを壊す様子を見よう:

script: >
  set -e
  echo hi
  echo bye

これは "set -e echo hi echo bye\n" に畳まれる。三つのコマンドが一行に。同じ塊を | にすれば "set -e\necho hi\necho bye\n" になり、それが意図どおりだ。迷ったら |。不要な改行を残しても、多くの場合は問題ない。必要な改行を失えばバグになる。

> が畳まないもの:深いインデントと空行

折り畳みには例外がある。知っておく価値がある。ブロックの他の行より深くインデントされた行は、畳まれない。自分の改行をそのまま保つ。見てみよう。

note: >
  prose that wraps
  onto two lines
    indented line, kept as-is
  back to prose

これは "prose that wraps onto two lines\n indented line, kept as-is\nback to prose\n" になる。折り返した散文はスペースに畳まれた。深くインデントした行は、改行もインデントも保った。だから折り畳みブロックの中に「リテラルの島」を置ける。説明を折り畳みつつ、その中にコード片を埋め込める。

これは罠でもある。うっかりスペースを一つ多く入れると、その行がリテラルになる。出力に、意図しない改行が一つ増える。しかも何の警告もない。

空行も > が畳まないものだ。折り畳みブロックの中の空行はつぶれず、改行になる。だから > でも段落を分けられる。段落の中の改行はスペースに畳まれるが、段落の間の空行は改行として残る。

text: >
  first paragraph

  second paragraph

これは "first paragraph\nsecond paragraph\n" になる。段落のの改行はスペースに畳まれる。段落と段落のあいだの空行は、改行のまま残る。

chomping:末尾の改行をいくつ残すか

|> を選んだあと、ブロックの末尾に小さな一件が残る。最後の行のあとの改行をどうするか。YAML はこれを「chomping(切り詰め)」と呼ぶ。三つのモードがある。

  • 既定(clip、一つ残す):末尾に空行をいくつ残そうと、改行はちょうど一つ。|"text\n" になる。
  • strip、-(すべて削る):末尾の改行を一つも残さない。|-"text" になる。
  • keep、+(すべて残す):書いた末尾の改行の数だけ残す。空行が二つ続くブロックに |+"text\n\n\n" になる。

たいていは既定で足り、意識もしない。効くのは二つの場面だ。一つ、末尾の改行が何かを壊すとき。ハッシュにかける値、トークン、ファイル名。そのときは - を使い、key: |- と書く。もう一つ、末尾の空行に意味があり、残さねばならないとき。そのときは +。それ以外は、触らない。

内容そのものがスペースで始まるとき

ここに一つ、見えにくい罠がある。YAML はブロックのインデントを、最初の非空行から決める。ふつうはそれでいい。だが内容が、本物のスペースで始まることもある。たとえば、すでにインデントされたコード片だ。YAML はその先頭のスペースをブロック自身のインデントと見なし、剥ぎ取る。さらに悪いことに、あとの行のインデントが浅ければ、ブロックは早く終わり、解析エラーになる。

対策はインデント指示子だ。|> の直後に数字を一つ置く。推測させず、インデントを明示する。

code: |2
      leading spaces kept
    less-indented line

その 2 は、絶対的な列ではなく、親のインデントからの追加分を表す。親のインデントから2スペース分先が、すべて内容になる。ここではキーが第0列にあるので、内容は第2列から始まる。同じキーをもう一段ネストすれば、|2 はその段からさらに2スペース先を指す。だから leading spaces kept は四スペースを保ち、 less-indented line は二スペースを保つ。数字は chomping とも組める。順序はどちらでもいい。|2-|-2 は同じ意味だ。

ブロックスカラーだけが方法ではない

記号を何も付けずに、複数行文字列を書くこともできる。plain(引用符なし)、単一引用符、二重引用符。どれも行をまたげる。この三つはどれも同じように畳む。二行のあいだの普通の改行はスペースになるが、空行はやはり改行になる。(「深いインデントの行を保つ」規則は > 独自のもので、ここには当てはまらない。)

plain:  first line
  second line
single: 'first line
  second line'
double: "first line
  second line"

三つとも "first line second line" になる。主な違いはエスケープ、引用、そして plain scalar 固有の構文上の制約にある。

二重引用符はエスケープを解釈する。"a\nb" は二行だ。"tab\there" には本物のタブがある。行末の一つのバックスラッシュは、次の行をスペースなしで繋ぐ。単一引用符は何も解釈しない。'a\nb' は、リテラルのバックスラッシュと n だ。単一引用符の中に引用符を一つ入れるには、二つ書く。'it''s here'it's here になる。ブロックスカラー(|>)はエスケープを一切解釈しない。中の \n はバックスラッシュと n だ。

五つを並べて見よう:

書き方改行\n エスケープ末尾の改行
| リテラル保つ不可一つ、- / + で調整
> 折り畳みスペースに畳む(空行は改行に。深いインデントの行は保つ)不可一つ、- / + で調整
"二重引用符"普通の改行はスペースに畳む;空行は改行になし
'単一引用符'普通の改行はスペースに畳む;空行は改行に不可('' で引用符一つ)なし
plain普通の改行はスペースに畳む;空行は改行に不可なし

この表から二つ分かる。本物の改行が欲しければ、エスケープなしで与えるのは | だけだ。\n\t のエスケープが欲しければ、読むのは二重引用符だけだ。

すべての行末は \n になる

もう一つ、忘れやすい正規化がある。YAML はスカラー内のすべての改行を \n に書き換える。Windows で CRLF で保存したファイルも、解析すれば \n であって \r\n ではない。ソースから復帰文字を密輸することはできない。どうしても必要なら、二重引用符の中でエスケープする。"line\r\n" だ。

実際のプロジェクトでどう噛みつくか

同じ規則が何度も出てくる。値そのものがファイルやスクリプトなら、それは | を求める。

  • Kubernetes の ConfigMap は、設定ファイル丸ごとを一つの値として埋め込む。その値はほぼつねに | だ。改行がファイルそのものだからだ。
  • GitHub Actions は shell のステップを run: | で書く。各行がコマンドなので、改行を残さねばならない。
  • Docker Compose と Ansible も、インラインのスクリプトやファイルで同じことをする。

> は反対の仕事に現れる。人が読む文章だ。長い description: フィールド。幅のために折り返したコメント。値が散文なら畳む。ファイルなら保つ。

ひとことで決める

  • 改行に意味はあるか。 ある → |。ない、ただの折り返し → >
  • 迷うなら? |。改行を保つのは安全、失うのはバグだ。
  • 改行が内容の構造の一部なら(コード・スクリプト・表・埋め込みファイル)| を使う。
  • \n\t のエスケープが要る? 二重引用符。リテラルの改行が要る? |
  • 末尾の改行は chomping で決まる。既定は \n 一つ、- はなし、+ はすべて。
  • 内容がスペースで始まる? インデント指示子を使う。|2 のように。

本当の文字列を見る

これらは YAML を読むだけでは分からない。\n とスペースは、紙の上では同じに見える。だから確かめる。ファイルを YAML コンバーターに貼り、JSON 出力を読む。改行はすべて明示的な \n として、折り畳みはスペースとして、末尾の改行はそのまま現れる。見えるのは、プログラムが受け取る文字列だ。ソースの見た目ではない。

これは設定フォーマット系列の一本だ。隣に、JSON・YAML・TOML の選び方の支柱記事と、YAML はなぜ壊れるのか(大声のエラーと静かな失敗、二つの壊れ方)がある。ブロックスカラーもまた、YAML が「妥当だが、思い込みとは違うこと」をする場所だ。直し方はいつも同じ。ソースの見た目を信じず、本当の値を見る。