11
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Shellでファイルテンプレート的な機能を実現

Last updated at Posted at 2019-02-14

発端

rubyの開発をする時、.erb などでテンプレートを記入して、いざ使う時変数展開(Rendering)すれば柔軟に対応できる。
しかしだいたいこういう機能はRuby、PythonやJavaScriptのような高水準言語でしか実装出来ない。Dockerのような極小システムを使う時、わざわざRubyをインストールするのもだるいし、Shellでなんとか対応してみた。

2019-2-15追記

@hnw さんがご指摘した通り、これはあくまでshellだけ作動する環境での一時対策であり、nginx:alpineなどDockerのイメージを使う場合は素直にenvsubstを使った方が効率的である。

TL;DR

shut-up-and-show-me-the-code.jpg

コアなファンクションは下記の四行です

while read line || [ -n "${line}" ]
do
  echo $(eval echo "''${line}")
done < ./template.txt

上記template.txtの中身は:

template.txt
This is ${USER}\\'s computer.
Home directory is ${HOME}.

Rendering結果は:

This is jerrylee's computer.
Home directory is /Users/jerrylee.

解説

原理

template.txtの内容を一行ずつ読み取り、${USER}のようなところを環境変数の該当変数で展開して吐き出す。
' などShellでの制御文字は \\' でエスケープする。いわば変数の二重展開である。

ここの変数の二重展開も少し特別のもので、ネット上は該当する記事は見つかっておらず、試行錯誤でやっとたどり着いたもの。

ネット上に出されたパターンをだいたい試してみた、結果はこちら。

echo $line
# `# File Template Demo` => `# File Template Demo`
# `home: ${HOME}` => `home: ${HOME}`

echo $(eval echo '$'${line}) # OR `echo $(eval echo '$'$line)`
# `# File Template Demo` => `0 File Template Demo`
# `home: ${HOME}` => `: /Users/jerrylee`

echo $(eval echo "'$'${line}")
# `# File Template Demo` => `$# File Template Demo`
# `home: ${HOME}` => `$home: /Users/jerrylee`

echo $(eval echo "${line}")
# `# File Template Demo` => `` # 改行だけでる、コメントアウトが効いたと思う
# `home: ${HOME}` => `home: /Users/jerrylee`

echo $(eval echo "''${line}")
# `# File Template Demo` => `# File Template Demo`
# `home: ${HOME}` => `home: /Users/jerrylee`

readline

別に一気にファイルを読み込んで処理しても構わないが、2箇所つまずく場所がある。

  • 改行、ファイルの改行は変数展開の時で予想外な動きをする可能性がある
  • メモリ使用量、大きなファイルを処理する時、無駄にメモリを使うのはよくない

|| [ -n "${line}" ]

この構文を追加すれば、仮にテンプレートの最後に改行がなくても、最後の行まで読み取れる。

実用例

render.sh
#!bin/sh

if [ ! -f $1 ]; then
  echo "Usage:\n$ sh render-console.sh template.tpl > target.txt"
  exit 1
fi

while read line || [ -n "${line}" ]
do
  echo $(eval echo "''${line}")
done < $1
template.txt
My name is ${MY_NAME}

上記 render.shtemplate.txt を作成したら、以下コマンドを実行する。

$ export MY_NAME=Alice; sh render.sh template.txt > result.txt

MY_NAME が定義され、result.txt が生成される。

result.txt
My name is Alice

余談

改めてShell Script はただのオバハンではないことがわかった の表現力に驚いた。

参考

https://askubuntu.com/a/121868
https://qiita.com/kod314/items/f8aa4929501882e97b38
https://orebibou.com/2015/01/シェルスクリプトでevalコマンドを用いた変数の2重/
https://qiita.com/Ets/items/a7fa24b138b8ee883dac
https://www.server-memo.net/shellscript/read-file.html
https://qiita.com/hnw/items/291090f8632f3b40e1a0

11
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?