如何推迟变量扩展

如何推迟变量扩展

我想用尚未设置的变量初始化脚本顶部的一些字符串,例如:

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

然后稍后PLACEEVENTACTION、 和RESULT将被设置。然后我希望能够在扩展变量的情况下打印出我的字符串。是我唯一的选择吗eval?这似乎有效:

eval "echo ${str1}"

这是标准吗?有一个更好的方法吗?eval考虑到变量可能是任何东西,最好不要运行。

答案1

对于您显示的输入类型,利用 shell 扩展将值替换为字符串的唯一方法是以eval某种形式使用。只要您控制 的值str1并且可以确保它仅引用已知为安全的变量(不包含机密数据)并且不包含任何其他未加引号的 shell 特殊字符,这就是安全的。您应该在双引号内或在此处文档中扩展字符串,这样才比较"$\`特殊(它们之前需要带有\in 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无论如何都没有必要,你甚至不需要两次评估你的变量。

确实,@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并且空值将保持原样。

范围:

您可能会注意到,在上面的示例中$PLACE$RESULT通过设置时会发生变化parameter expansion虽然_top_of_script_pr()已经被调用,大概在运行时设置它们。这有效的原因是_top_of_script_pr()是一个( subshelled )函数 - 我将其包含在parens而不是{ curly braces }用于其他人。因为它是在子 shell 中调用的,所以它设置的每个变量都是locally scoped当它返回到其父 shell 时,这些值就会消失。

但当_more_important_function()$ACTION这是globally scoped所以它影响_less_important_function()'s第二次评价$ACTION因为_less_important_function()$ACTION仅通过${parameter:=expansion}.

:无效的

为什么我使用领先:colon?嗯,man页面会告诉你: does nothing, gracefully.你看,parameter expansion正是它听起来的样子 - 它expands的值${parameter}.所以当我们设置一个变量时${parameter:=expansion}我们留下了它的值 - shell 将尝试内嵌执行该值。如果它尝试运行the cemetery它只会向你吐出一些错误。PLACE="${PLACE:="the cemetery"}"会产生相同的结果,但在这种情况下也是多余的,我更喜欢 shell: ${did:=nothing, gracefully}.

它确实允许您执行以下操作:

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

这里的文件

顺便说一句 - null 或 unset 变量的内联定义也是以下方法有效的原因:

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

最好的思考方式是here-document 作为流式传输到输入文件描述符的实际文件。或多或少这就是它们的本质,但是不同的 shell 实现它们的方式略有不同。

无论如何,如果您不引用<<LIMITER你得到它的流式传输评估为expansion.所以在a中声明一个变量here-document可以工作,但只能通过expansion这限制您只能设置尚未设置的变量。尽管如此,正如您所描述的那样,这完全适合您的需求,因为当您调用模板打印函数时,将始终设置默认值。

为什么不eval?

嗯,我提出的例子提供了一种安全有效的接受方式parameters.因为它处理范围,所以 set via 中的每个变量${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)\"."

相关内容