web-dev-qa-db-ja.com

変数の展開を延期する方法

次のように、まだ設定されていない変数を使用して、スクリプトの上部にあるいくつかの文字列を初期化したいと思っていました。

str1='I went to ${PLACE} and saw ${EVENT}'
str2='If you do ${ACTION} you will ${RESULT}'

その後、PLACEEVENTACTIONRESULTが設定されます。次に、変数を展開した状態で文字列を出力できるようにしたいと思います。私の唯一のオプションはevalですか?これはうまくいくようです:

eval "echo ${str1}"

この基準は何ですか?これを行うより良い方法はありますか?変数が何であれ、evalを実行しないのは良いことです。

19
Aaron

表示する入力の種類で、シェル展開を利用して値を文字列に置き換える唯一の方法は、evalを何らかの形で使用することです。これは、str1の値を制御し、安全である(機密データを含まない)変数のみを参照し、引用符で囲まれていないシェルの特殊文字が含まれていない限り、安全です。二重引用符内またはヒアドキュメント内の文字列を展開する必要があります。これにより、"$\`のみが特別になります(\str1を前に付ける必要があります)。

eval "substituted=\"$str1\""

文字列の代わりに関数を定義する方がはるかに堅牢です。

fill_template () {
  sentence1="I went to ${PLACE} and saw ${EVENT}"
  sentence2="If you do ${ACTION} you will ${RESULT}"
}

変数を設定してから、関数fill_templateを呼び出して出力変数を設定します。

PLACE=Sydney; EVENT=fireworks
ACTION='not learn from history'; RESULT='have to relive history'
fill_template
echo "During my holidays, $sentence1."
echo "Cicero said: \"$sentence2\"."

展開されていない変数の代わりに、文字列テンプレートのプレースホルダーを使用できます。これはかなり乱雑になります。テンプレートの負荷が非常に高い場合は、実際のテンプレートライブラリを持つ言語を検討することをお勧めします。

format_template() {
    changed_str=$1

    for Word in $changed_str; do
        if [[ $Word == %*% ]]; then
            var="${Word//\%/}"
            changed_str="${changed_str//$Word/${!var}}"
        fi
    done
}

str1='I went to %PLACE% and saw %EVENT%'
PLACE="foo"
EVENT="bar"
format_template "$str1"
echo "$changed_str"

上記の欠点は、テンプレート変数が独自のWordでなければならないことです(たとえば、"%prefix%foo")。これは、いくつかの変更を加えるか、動的ではなくテンプレート変数をハードコーディングすることで修正できます。

1
jordanm