やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

bashテンプレートエンジンmo(mustache)

 気になっていたので試す。

情報源

手順

インストール・ダウンロード・テスト実行

curl -sSL https://git.io/get-mo -o mo
chmod +x mo
sudo mv mo /usr/local/bin/
export NAME="山田"
echo "My name is {{NAME}} ." | mo

ヘルプ

$ mo --help
Mo is a mustache template rendering software written in bash.  It inserts
environment variables into templates.

Simply put, mo will change {{VARIABLE}} into the value of that
environment variable.  You can use {{#VARIABLE}}content{{/VARIABLE}} to
conditionally display content or iterate over the values of an array.

Learn more about mustache templates at https://mustache.github.io/

Simple usage:

   mo [OPTIONS] filenames...

Options:

   -u, --fail-not-set
         - Fail upon expansion of an unset variable.
   -e, --false
         - Treat the string "false" as empty for conditionals.
   -h, --help
         - This message.
   -s=FILE, --source=FILE
         - Load FILE into the environment before processing templates.

MO_VERSION=2.0.4
Moはbashで書かれた口ひげのテンプレートレンダリングソフトウェアです。 挿入する
環境変数をテンプレートにまとめます。

簡単に言うと、moは{{VARIABLE}}をその値に変更します。
環境変数 {{#VARIABLE}}コンテンツ{{/ VARIABLE}}を使用すると、
条件付きで内容を表示するか、配列の値を繰り返し処理します。

口ひげテンプレートの詳細については、https://mustache.github.io/を参照してください。

簡単な使い方

    mo [OPTIONS]ファイル名...

オプション:

    -u、 -  fail-not-set
           - 未設定変数の展開時に失敗する。
    -e、 -  false
           - 条件付きの場合、文字列 "false"を空として扱います。
    -h、 -  help
          - このメッセージ。
    -s = FILE、 -  source = FILE
           - テンプレートを処理する前にFILEを環境にロードします。

 変数。

export NAME="Andy"; echo "My name is {{NAME}}." | mo
export NAME="Andy"; echo "My name is {{  NAME  }}." | mo

 配列。

export ARRAY=( A B C ); echo -e "{{#ARRAY}}\n* {{.}}\n{{/ARRAY}}" | mo
export ARRAY=( A B C ); echo -e "{{  #ARRAY  }}\n* {{  .  }}\n{{  /ARRAY  }}" | mo

 連想配列。(連想配列export環境変数にできないため逆にmoをロードする。よって. mo必須)

. mo; declare -A OBJ=(["name"]="Jhon" ["age"]="20"); echo -e "My name is {{OBJ.name}}.\nI am {{OBJ.age}} years old." | mo

 連想配列の配列。(テンプレートの__HUMAN__をそれぞれの連想配列名にリネームし直さないと参照できない。そのためグローバル変数Humansを使う(変数名の二重管理。名前汚染))

EveryHuman() {
    local -r content=$(cat)
    for human in "${Humans[@]}"; do
        echo "$content" | sed "s/__HUMAN__/${human}/"
    done
}

. mo
declare -A h0 h1 h2
h0=([name]=Yamada [age]=11)
h1=([name]=Sasaki [age]=22)
h2=([name]=Tanaka [age]=33)
Humans=(h0 h1 h2)
cat <<EOS | mo
{{#EveryHuman}}
Name: {{__HUMAN__.name}}
Age: {{__HUMAN__.age}}
{{/EveryHuman}}
EOS

 ワンライナーにすると以下。

EveryHuman() { local -r content=$(cat); for human in "${Humans[@]}"; do echo "$content" | sed "s/__HUMAN__/${human}/"; done; }; . mo; declare -A h0 h1 h2; h0=([name]=Yamada [age]=11); h1=([name]=Sasaki [age]=22); h2=([name]=Tanaka [age]=33); Humans=(h0 h1 h2); echo -e '{{#EveryHuman}}\nName: {{__HUMAN__.name}}\nAge: {{__HUMAN__.age}}\n{{/EveryHuman}}' | mo

 関数。(export -fで関数を環境変数にできる。. moでシェル変数参照しても可)

F() { echo AAA; }; export -f F; echo '{{#F}}{{/F}}' | mo
. mo; F() { echo AAA; }; echo '{{#F}}{{/F}}' | mo

 名前汚染を防ぐにはbash -cコマンドでサブプロセス化してやればいい。(環境変数は使わず)

bash -c ". mo; AGE=20; echo 'I am {{AGE}} years old.' | mo"

 {{! ... }}でコメント。(出力されない) スペース必須。

echo "{{! コメント }}" | mo
$ echo "{{!コメント}}" | mo
bash: !コメント}}: event not found

 ファイル参照。(テンプレート)

export NAME="Andy"; echo "{{> parts.txt}}" | mo

parts.txt

My name is {{NAME}}.

 ファイル参照。(環境変数

unset NAME; echo "{{> parts.txt}}" | mo -s=env.txt

env.txt

export NAME="Andy"

未定義時の挙動

空白

unset NAME; echo "My name is {{NAME}}." | mo

エラー

unset NAME; echo "My name is {{NAME}}." | mo -u
My name is Env variable not set: NAME

定義済み・未定義のときの値設定

unset NAME; echo "{{#NAME}}My name is {{NAME}}.{{/NAME}}{{^NAME}}NAMEは未定義です。{{/NAME}}" | mo
書式 意味
{{#NAME}}〜{{/NAME}} 定義済み時の出力
{{^NAME}}〜{{/NAME}} 未定義時の出力
$ export NAME='Andy'; echo "{{#NAME}}My name is {{NAME}}.{{/NAME}}{{^NAME}}NAMEは未定義です。{{/NAME}}" | mo
My name is Andy.
$ unset NAME; echo "{{#NAME}}My name is {{NAME}}.{{/NAME}}{{^NAME}}NAMEは未定義です。{{/NAME}}" | mo
NAMEは未定義です。

 demoにもない罠があった。

テンプレート参照

 moでテンプレート変数を参照するには以下の方法しかない。

  • export環境変数にする
  • . moでシェル変数を読み込めるようにする

 . moせずシェル変数を宣言しても参照できず空白になる。

AGE=20; echo "I am {{AGE}} years old." | mo

env不可

 envコマンドでサブシェルに変数を渡して使うことはできなかった。

$ env AGE=20 echo "I am {{AGE}} years old." | mo
(AGEが参照されず空値)
$ env AGE=20 . mo && echo "I am {{AGE}} years old." | mo
env: `.': 許可がありません
$ sudo env AGE=20 sudo . mo && echo "I am {{AGE}} years old." | mo
sudo: .: コマンドが見つかりません
$ sudo env AGE=20 sudo source mo && echo "I am {{AGE}} years old." | mo
sudo: source: コマンドが見つかりません

set -u. moすると未割当エラー

  • set -Ceu(set -u)で. moすると未割当エラーになってしまう
    • /usr/local/bin/mo: 行 166: moArgs: 未割り当ての変数です
      • mo内でset -Ceuすることなく未割当せずに実装すれば. moしても影響は最小限で済むのに……

サニタイズ(HTMLエスケープ)しない

 デフォルトでサニタイズするという情報があるが私の環境ではしていない。

export HTML="<html></html>"; echo "{{HTML}}" | mo
export HTML="<html></html>"; echo "{{{HTML}}}" | mo

 サニタイズされたら以下のように表示されるはず。

&lt;html&gt;&lt;/html&gt;

 しかし端末ではどちらもサニタイズされずに表示された。

<html></html>

 考えられるのは以下。

  • mustacheのバージョン差
  • bash用mustacheの仕様

要望

  • テンプレート内容に含まれる変数の列挙
    • どの変数に何の値を代入するか問うUIを出したいので
  • 環境変数でなくテキストデータでも渡せるようにしたい(標準入力 or ファイル or プロセス置換)
    • KeyValue(TSV形式)
    • 配列(指定デリミタ(newline, space, comma, ...))
    • 連想配列の配列(TSV形式)