はじめに
諸般の事情により、以下のようなリストについて、
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が利用できる状況はかなり限定される。 せめて強制的にクォートするようなフラグがあれば、使いようがあるのだが。