変数拡張を延期する方法

変数拡張を延期する方法

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

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

その後、後で、、、およびPLACE設定されます。その後、拡張変数を使用して文字列を印刷できるようにしたいです。それは私の唯一の選択ですか?これはうまくいくようです:EVENTACTIONRESULTeval

eval "echo ${str1}"

これは標準ですか?もっと良い方法がありますか?eval変数が何でもよいことを考慮すると、実行しないことが最善です。

答え1

表示される入力タイプの場合、シェル拡張を利用して値を文字列に置き換える唯一の方法は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とにかく必要はなく、変数を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()再:

墓地に行ってディズニーオンアイス(Disney on Ice)を見ました。

数学を課外すると後悔。

仕組み:

ここで重要な特徴はコンセプトです。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()'s2回目のレビュー$ACTIONなぜなら_less_important_function()セット$ACTION通じるだけ${parameter:=expansion}.

:無効

なぜ私はリードを使用するのですか?:colon?まあ、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.したがって、変数を宣言します。here-documentうまくいきますが、次のようにのみ可能です。expansionこれはまだ設定されていない変数を設定することに限定されます。ただし、説明したようにテンプレート印刷機能を呼び出すときはデフォルト値が常に設定されているため、これはお客様のニーズに最適です。

なぜできないのeval?

さて、私が提示した例は、安全で効率的な受け入れ方法を提供します。parameters.範囲を扱うため、集合の各変数は${parameter:=expansion}外部で定義できます。したがって、これらすべてを template_pr.sh というスクリプトに入れて、次を実行すると:

 % RESULT=something_else template_pr.sh

あなたは得るでしょう:

あなたのお母さんの家に行き、ディズニーのアイスを見ました。

カリグラフィを練習すると何かが起こる

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

数学を落とすと何かが得られるだろう

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

数学を落とすと何かが得られるだろう

これは、スクリプトに文字通り設定された変数には機能しません。$EVENT, $ACTION,そして$one,しかし、私は違いを示すためにこのように定義しました。

とにかく不明な入力を受け入れます。evaled文は本質的に安全ではありません。parameter expansionこの目的のために特別に設計されています。

答え3

拡張されていない変数の代わりに文字列テンプレートのプレースホルダを使用できます。これはすぐに混乱する可能性があります。実行する作業このテンプレートが非常に多い場合は、実際のテンプレートライブラリがある言語を検討できます。

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"

上記の欠点は、テンプレート変数が独自の単語でなければならないことです(例:you can't do "%prefix%foo")。これはいくつかの修正によって解決されるか、動的に代わりにテンプレート変数をハードコーディングすることで解決できます。

答え4

私はこれに基づいています受け入れられた回答そして少し簡単にしてください。fill_template電話が少しぎこちないようです。環境変数の代わりに関数を使用するには少し異なる構文が必要ですが(場合によっては機能しない可能性があります)、IMOではそれを使用するコードを制御するとよりきれいです。

「変数」を関数として定義します。

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

次に、次のように使用します。

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

関連情報