template_engine.sh bashテンプレートエンジン coneta #5

#**template_engine.sh**

　bashで使うテンプレートエンジン[^1]です。
[^1]: [wikipedia](https://ja.wikipedia.org/wiki/%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%B3)によると、テンプレートエンジンはテンプレートと呼ばれる雛形と、あるデータモデルで表現される入力データを合成し、成果ドキュメントを出力するソフトウェアまたはソフトウェアコンポーネントである。」とのことです。

#**機能**
 - 「テンプテートファイル」の「被置換文字列」を「置換文字列」で置換後、「出力方法」に従って、結果を出力します。
 - 「テンプテートファイル」、「被置換文字列」、「置換文字列」、「出力方法」は「データファイル」で定義します。
 - 「データファイル」に任意のbashコマンドを記述(※)することにより、テンプレートエンジンの振る舞いを変更することがでます。  
※　1行で記述する必要がある。複数のコマンドを記述する場合は`;`を使用します。  
    　　コマンドは`bash`の`eval`で評価されます。
 - 「データファイル」の読み込み、「テンプレートファイル」の読み込み、「出力」はデフォルトで`cat`コマンドを使用していますが、環境変数にコマンドを設定することにより、別のコマンドに変更することができます。  
※　この機能は[extcat](https://qiita.com/EjiriAkira/items/30bfb2520744094a3679)を使うことを想定して実装しましたが、他のコマンドを使うことも可能です。extcatは本スクリプトに組み込んであります。








| No. | 環境変数 |対象となる機能|
|:-:|:--|:--|
| 1  |CAT_DATA|データファイルの読み込み|
| 2  |CAT_TEMPLATE|テンプレートファイルの読み込み|
| 3  |CAT_OUTPUT|出力|









#**使用方法**

#####**【実行方法】**

~~~
【データファイルを引数で指定する】
　template_engine.sh [file [file]...]

【データファイルを標準入力に流し込む】
  cat file | template_engine.sh
~~~

#####**【データファイルの書式】**

~~~code:データファイル
CMD bashコマンド　
DEF テンプレートファイル 被置換文字列1△被置換文字列2△被置換文字列n
DAT 出力方法            置換文字列1△置換文字列2△置換文字列n
~~~

 CMD行：任意、複数行可。
 DEF行：必須、以降のDAT行の被置換文字列、複数行可。
 DAT行：必須、直前のDEF行の置換文字列、複数行可。
 出力方法：
　　> ファイル　#ファイルに上書き
　　>> ファイル　#ファイルに追加
 △：デフォルトはタブコード(0x09)です。環境変数に設定することにより、別の文字に変更することができます。






| No. | 環境変数 |対象となる機能|
|:-:|:--|:--|
| 1  |DAT_IFS|DAT行の読み込み|
| 2  |DEF_IFS|DEF行の読み込み|

 被置換文字列：sedコマンドの正規表現
 置換文字列：シングルクォート(0x27)とタブコード(0x09)以外の文字はそのまま記述します。
　　シングルクォート(0x27)は以下を記述します。
　　     　　`'\\\\\\\\''`
　　タブコード(0x09)はCMD行で環境変数にタブコードを設定し、DAT行でその環境変数を参照します。
　　     　　`export XTAB='△'`　△はタブコード、環境変数`XTAB`は任意です、他の名前でも可。
　　     　　`'\\\\\\\"${XTAB}"'`

















#####**【関連図】**

![template_engine.png](https://qiita-image-store.s3.amazonaws.com/0/234070/dbe7a2b0-df65-0e74-8f3b-0949bfd279ef.png)
※図中のsedのsコマンドは区切り文字に`/`を使用していますが、実際は0x04を使用しています。


<details><summary>関連図のdotソース</summary><pre>digraph xxx{
	node[shape=record fontname="MS Gothic"]
	TEMPLATE_ENGINE[label="template_engine.sh　データファイル"]
	DATA_FILE
	     [label="{ データファイル|{CMD| bashコマンド　　　　　　　　　　　　　　　　　}|{DEF|<f1> テンプレートファイル| 被置換文字列（複数可）　}|{DAT|<f2> 出力方法\l \>出力ファイル　\l\>\>出力ファイル　　　\l| 置換文字列（複数可）　　}}"];
	TEMPLATE_FILE[label=" テンプレートファイル"]
	OUTPUT[label=" 出力ファイル"]
	edge [arrowhead=open style=dotted]
	DATA_FILE:f1->TEMPLATE_FILE
	DATA_FILE:f2->OUTPUT
	TEMPLATE_ENGINE->DATA_FILE
	edge [arrowhead=normal style=solid]
	TEMPLATE_FILE->OUTPUT[ fontname="MS Gothic" label="sed s/被置換文字列/置換文字列/g" color="green"]
}
</pre></details>













#**実行例**

#####**【テンプレートファイル】**
~~~:template_file.txt
########################################
# @1@ @2@
# @2@ @1@
~~~




#####**【データファイル】**
~~~:data.txt
CMD     rm result.txt 2> /dev/null
DEF     template_file.txt       "@1@"   "@2@"
DAT     >>result.txt    "DATA 11        DATA21
DAT     >>result.txt    "DATA 12        ${HOME}DATA22
DAT     >>result.txt    "DATA 13'\\\\\\\\''     DATA23
CMD     export XTAB='   '
DAT     >>result.txt    "DATA 14'\\\\\\\"${XTAB}"'      DATA24
DAT     >>result.txt    DATA 15!"#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~@@@ DATA25
~~~


#####**【実行】**
~~~bash
template_engine.sh data.txt 
~~~

#####**【結果】**
~~~:result.txt
########################################
# "DATA 11 DATA21
# DATA21 "DATA 11
########################################
# "DATA 12 ${HOME}DATA22
# ${HOME}DATA22 "DATA 12
########################################
# "DATA 13' DATA23
# DATA23 "DATA 13'
########################################
# "DATA 14       DATA24
# DATA24 "DATA 14 
########################################
# DATA 15!"#$%@1@()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~@@@ DATA25
# DATA25 DATA 15!"#$%@1@()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~@@@
~~~

#**実装**

~~~bash:template_engine.sh
#!/bin/bash
extcat () { cat $@ | bash <<< "cat <<< "\""$(cat)"\""" ; };export -f extcat
export CAT_DATA=${CAT_DATA:-"cat"}
export CAT_TEMPLATE=${CAT_TEMPLATE:-"cat"}
export CAT_OUTPUT=${CAT_OUTPUT:-"cat"}
export DEF=()
export CMD
export DAT
export TEMPLATE_FILE
export OUTPUT
export TAB_CODE=$(printf "\t")
main_proc(){
	local line kind rest
	${CAT_DATA} $@ | sed '/^#/d' |  while read line
	do
		read kind rest <<< "${line}"
		case "${kind}" in
			DEF)	func_DEF "${rest}";;
			DAT)	func_DAT "${rest}";;
			CMD)	func_CMD "${rest}";;
			*)
		esac
	done
}
func_DEF(){
	local line old_ifs rest i d
	line=$1
	old_ifs=${IFS}
	IFS=${DEF_IFS:-${TAB_CODE}}
	read TEMPLATE_FILE rest <<< "${line}"
	i=0
	unset DEF[@] 
	for d in ${rest}
	do
		eval DEF[i]="${d}"
		((i+=1))
	done
	IFS=${old_ifs}
}
func_DAT(){
	local line old_ifs rest i d
	local sed_s_command_delimiter=$(echo -n -e '\x04')
	line=$1
	old_ifs=${IFS}
	IFS=${DAT_IFS:-${TAB_CODE}}
	read OUTPUT rest <<< "${line}"
	i=0
	{
		printf "${CAT_TEMPLATE} ${TEMPLATE_FILE} | sed '\n"
		for d in ${rest}
		do
			printf 's'${sed_s_command_delimiter}'%s'${sed_s_command_delimiter}'%s'${sed_s_command_delimiter}'g\n' "${DEF[i]}" "${d}"
			((i+=1))
		done
		printf "' | ${CAT_OUTPUT} ${OUTPUT}\n"
	} | bash
	IFS=${old_ifs}
}
func_CMD(){
	eval "$1"
}
main_proc $@
~~~



#**環境**


ホスト　Windows10 COREi7 
VM　　　VirtualBox バージョン 5.2.8 r121009 (Qt5.6.2)
　　　　　CentOS Linux release 7.4.1708 (Core)
　　　　　3.10.0-693.21.1.el7.x86_64 #1 SMP Wed Mar 7 19:03:37 UTC 2018
GNU bash, バージョン 4.2.46(2)-release (x86_64-redhat-linux-gnu)

---