記事

Last Modified:

設定ファイルにシェルスクリプトを書けるようにする方法 #Shell

設定ファイルのとある部分を置換したい。 巷でよくある方法としては{{REPLACEMENT_TARGET}}のように書いてsed -e 's/{{REPLACEMENT_TARGET}}/REPLACEMENT/'で置換する方法がある。 今回はさらにコマンド実行結果で置換したいと思ったので、REPLACEMENT_TARGETをシェルで再評価しないといけないのでこの方法だと上手くいかない。 がんばって再評価できるようにスクリプトを書いたとしても、 $VAR,${VAR},${VAR#foo}などなど多様な記述が可能なシェルスクリプトを再現するのは至難の技。

解説

冒頭では「設定ファイルのとある部分」としたが、要はシェルスクリプトを含んだ文字列を再評価できれば良い。 原理としては以下のようにシェルに食わせるスクリプトは以下の形になる。

$ set -x
$ str='I am $USER.'                 # シェルスクリプトを含んだ再評価したい文字列
+ str='I am $USER.'
$ reevaluate="echo \"$str\""           # 再評価するために実行可能なスクリプトを動的生成する
+ reevaluate='echo "I am $USER."'
$ eval "$reevaluate"
+ eval 'echo "I am $USER."'
+ echo 'I am robario.'
I am robario.

ヒアストリングを利用して複数行に対応させる。クォートの挙動には注意を払う。 当然だけど再評価したいわけだからEOFはクォートしない。

script="$(printf 'cat - <<EOF\n%s\nEOF\n' "$str")"
# これで生成したい再評価スクリプトの文字列が得られる
# script='cat - <<EOF
# I am $USER.
# EOF
# '
eval "$script"    # 再評価

以下の記事で紹介したawsddnsでは EC2インスタンス起動時および停止時に自動でDNS設定を行なう簡単な方法 #AWS @robario

PublicIpを取得している部分は、特定のキーワードリテラルを置換する方法も考えたけど、 色々できた方が面白そうだと思ってシェルスクリプトを埋め込めるように工夫した。

# Reevaluate as shell string
RecordSetGroups="$(printf 'cat - <<END_OF_REEVALUATE_STRING\n%s\nEND_OF_REEVALUATE_STRING\n' "$RecordSetGroups")"
RecordSetGroups="$(eval "$RecordSetGroups")"

のように使用しており、Metadata内で'$(curl --silent http://169.254.169.254/latest/meta-data/public-ipv4)'と書けるようにしている。 もちろんあらゆるスクリプトが実行可能なので、セキュリティには十分気を付けて自己責任で。