変数展開を延期する方法


18

スクリプトの先頭にあるいくつかの文字列を、まだ設定されていない変数で初期化したいと思っていました。

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

そしてその後にPLACEEVENTACTION、およびRESULT設定されます。次に、変数を展開して文字列を出力できるようにします。私の唯一のオプションevalですか?これはうまくいくようです:

eval "echo ${str1}"

これは標準ですか?これを行うためのより良い方法はありますか?eval変数が何であってもよいと考えて実行しないのはいいことです。

回答:


23

表示する入力の種類によって、シェル展開を活用して値を文字列に置き換える唯一の方法は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\"."

2
評価を遅らせ、明示的なeval呼び出しを避けるために関数を使用する素晴らしい仕事。
クレイトンスタンリー

良い解決策、これは私を大いに助けました。ありがとう!
スチュアート

8

私はあなたの意味を理解しているので、これらの答えが正しいとは思いません。eval決して必要ではありません。また、変数を2回評価する必要もありません。

確かに、@ Gillesは非常に近づいていますが、値をオーバーライドする可能性の問題と、複数回必要な場合の使用方法については触れていません。結局のところ、テンプレートは複数回使用する必要がありますよね?

重要なのは、あなたがそれらを評価する順番にあると思います。以下を考慮してください。

ここで、いくつかのデフォルトを設定し、呼び出されたときにそれらを印刷する準備をします...

#!/bin/sh
    _top_of_script_pr() ( 
        IFS="$nl" ; set -f #only split at newlines and don't expand paths
        printf %s\\n ${strings}
    ) 3<<-TEMPLATES
        ${nl=
}
        ${PLACE:="your mother's house"}
        ${EVENT:="the unspeakable."}
        ${ACTION:="heroin"}
        ${RESULT:="succeed."}
        ${strings:="
            I went to ${PLACE} and saw ${EVENT}
            If you do ${ACTION} you will ${RESULT}
        "}
    #END
    TEMPLATES

中間

これは、結果に基づいて印刷機能を呼び出す他の機能を定義する場所です...

    EVENT="Disney on Ice."
    _more_important_function() { #...some logic...
        [ $((1+one)) -ne 2 ] && ACTION="remedial mathematics"
            _top_of_script_pr
    }
    _less_important_function() { #...more logic...
        one=2
        : "${ACTION:="calligraphy"}"
        _top_of_script_pr
    }

これですべてのセットアップが完了したので、ここで実行して結果を取得します。

    _less_important_function
    : "${PLACE:="the cemetery"}" 
    _more_important_function
    : "${RESULT:="regret it."}" 
    _less_important_function    

結果

すぐにその理由を説明しますが、上記を実行すると次の結果が得られます。

_less_important_function()'s ファーストラン:

私はあなたの母の家に行き、ディズニー・オン・アイスを見ました

書道をすれば成功するでしょう。

それから _more_important_function():

私は墓地に行き、ディズニー・オン・アイスを見ました。

矯正数学をすれば成功します。

_less_important_function() 再び:

私は墓地に行き、ディズニー・オン・アイスを見ました。

矯正数学を行う場合、それ後悔します。

使い方:

ここでの主要な機能は、次conditional ${parameter} expansion.の形式を使用して変数が設定されていないかnullである場合にのみ変数に値を設定できるという概念です。

${var_name:=desired_value}

代わりに、未設定の変数のみを設定する場合は、省略し:colon、null値はそのまま残ります。

範囲:

上の例$PLACEでは、すでに呼び出されているにもかかわらず、$RESULT経由parameter expansion_top_of_script_pr()設定されると、おそらく実行時に設定されて変更されることに気付くかもしれません。これが機能する理由は、それ_top_of_script_pr()( subshelled )関数であるということです-私はそれを他のparensものに{ curly braces }使用するのではなく、それで囲みました。サブシェルで呼び出されるため、設定するすべての変数はそうでlocally scopedあり、親シェルに戻るとそれらの値は消えます。

しかし、とき_more_important_function()セット$ACTION、それがあるglobally scopedことが影響して_less_important_function()'sの第二の評価$ACTION理由_less_important_function()のセット$ACTIONのみを経由し${parameter:=expansion}.

:ヌル

そして、なぜ私は先頭の:colon?Well を使用するのですか、manページは: does nothing, gracefully.あなたが見ると、parameter expansionそれがどのように聞こえるかを教えてくれます- 変数expandsの値${parameter}.を設定する${parameter:=expansion}と、その値が残ります-シェルはインラインで実行しようとします。実行しようとするthe cemeteryと、エラーが発生します。PLACE="${PLACE:="the cemetery"}"同じ結果が得られますが、この場合も冗長であり、シェルが: ${did:=nothing, gracefully}.

これを行うことができます:

    echo ${var:=something or other}
    echo $var
something or other
something or other

こちらのドキュメント

ところで-nullまたは未設定の変数のインライン定義も、以下が機能する理由です:

    <<HEREDOC echo $yo
        ${yo=yoyo}
    HEREDOC
yoyo

を考える最良の方法here-document は、入力ファイル記述子にストリーミングされる実際のファイルとしてです。多かれ少なかれそれは彼らのものですが、異なるシェルはそれらをわずかに異なって実装します。

いずれにせよ、引用符で囲まない場合、<<LIMITERそれをストリームし評価するexpansion.ので、aで変数を宣言するhere-documentことはできますが、それを介しexpansionてのみ、まだ設定されていない変数のみを設定できます。それでも、テンプレートの印刷関数を呼び出すとデフォルト値が常に設定されるため、これは説明したとおりのニーズに完全に適合します。

何故なの eval?

さて、提示した例は、parameters.スコープを処理するため、set via内のすべての変数${parameter:=expansion}は外部から定義できるため、安全で効果的な受け入れ方法を提供します。したがって、これらすべてをtemplate_pr.shというスクリプトに入れて実行した場合:

 % RESULT=something_else template_pr.sh

あなたが得るだろう:

私はあなたの母親の家に行き、ディズニー・オン・アイスを見ました

書道をすれば、something_else

墓地に行ってディズニー・オン・アイスを見た

修復数学を行うと、something_elseになります

墓地に行ってディズニー・オン・アイスを見た

修復数学を行うと、something_elseになります

これは、スクリプト内で文字通りに設定された変数やなどでは機能$EVENT, $ACTION,$one,ませんが、違いを示すためにそのように定義しただけです。

いずれにせよ、evaledステートメントへの未知の入力の受け入れは本質的に安全ではありませんが、parameter expansionそうするように特別に設計されています。


1

展開されていない変数の代わりに、文字列テンプレートにプレースホルダーを使用できます。これはすぐに面倒になります。あなたがやっていることが非常に重いテンプレートであるなら、あなたは本当のテンプレートライブラリを持つ言語を検討したいかもしれません。

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"

上記の欠点は、テンプレート変数がそれ自身の単語でなければならないことです(たとえば、できません"%prefix%foo")。これは、修正するか、テンプレート変数を動的に変更する代わりにハードコーディングするだけで修正できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.