Ansibleにおいて、リストの各要素をシングルクォートで囲んでからカンマで連結

はじめに

諸般の事情により、以下のようなリストについて、

terms:
  - AI
  - Artificial Intelligence

以下のように加工する必要に迫られた。

"'AI', 'Artificial Intelligence'"

つまり、それぞれの要素をシングルクォートで囲って、カンマで連結するとともに、全体をダブルクォートで囲むという加工を行う。 フィルタの使い方がややこしいのに加えて、ハマりどころもあるので、調査記録を残しておく。

最終的に出来上がったPlaybook

試行錯誤した結果、Playbookの内容は以下に落ち着いた。

- set_fact:
    single_quote: "'"

- shell: echo "{{ terms | map('regex_replace', '(^|$)', single_quote) | join(', ') }}"

モジュールdebugを利用して出力した結果の抜粋は以下。

ok: [192.168.8.8] => {
    "msg": "'AI', 'Artificial Intelligence'"
}

フィルタmapを利用することで、リストの各要素にフィルタregex_replaceを適用してシングルクォートで囲む加工を実現している。

また、置換後の文字列であるシングルクォートは、変数を利用して間接的に指定している。

ハマりどころ1 - シングルクォートを即値で表現できない

シングルクォートを即値で表現できないか色々試したが、どれもダメだった。 例えば、以下のようにシングルクォートを指定すると、文法エラーが発生する。

- shell: echo "{{ terms | map('regex_replace', '(^|$)', "'") | join(', ') }}"

全体をダブルクォートで囲まなければ文法エラーにはならないのだが、それでは望んだ結果は得られない。 バックスラッシュでエスケープできないか試したが、これもダメだった。 このもどかしさは、シェルスクリプトでありがちな、クォートやエスケープがネストすると訳が分からなくなるパターンと同じ。 一気に全部作ろうとせずに、タスクを分割して部分的に組み上げていくと、即値で書けるかもしれない。 しかし、中間的な作業変数は作りたくないので、シングルクォートの定数(みたいなもの)を準備することにした。

ハマりどころ2 - 標準のフィルタquote

標準でquoteというフィルタがあり、ぱっと見はこれで良さそうに思える。 フィルタquoteを利用したPlaybookは以下のようになる。

- shell: echo "{{ terms | map('quote') | join(', ') }}"

モジュールdebugを利用して出力した結果の抜粋は以下。

ok: [192.168.8.8] => {
    "msg": "AI, 'Artificial Intelligence'"
}

Artificial Intelligenceはシングルクォートで囲まれているのに、AIは囲まれていない。 調べてみると、Ansibleの問題報告に同じような事例Possible quote filter issue #28084を発見。 Ansibleの中の人のコメントは以下。

So the quote filter decides that a string with an asterisk needs to be quoted, but "/mnt" is fine without quotes. Which is correct for shell usage.

I don't think the quote-filter is what you need to use for your use-case as your intended output is not specific to the shell.

自分なりに意訳すると以下。

そのため、フィルタquoteは、アスタリスクを含む文字列についてはクォートが必要と判断しているが、"/mnt"についてはクォートがなくても問題は起きないと判断している。 フィルタquoteはシェルのために利用するので、これは正しい動作だ。

あなたのユースケースでは、出力結果はシェルでの利用を意図していないので、フィルタquoteは望んでいるものではないと思う。

ということで、フィルタquoteはシェルが解釈することを前提に、クォートするかどうかを独自に判断している。 そのため、フィルタquoteが利用できる状況はかなり限定される。 せめて強制的にクォートするようなフラグがあれば、使いようがあるのだが。

参考