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 が「妥当だが、思い込みとは違うこと」をする場所だ。直し方はいつも同じ。ソースの見た目を信じず、本当の値を見る。