如何推迟变量扩展


18

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

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

再后来PLACEEVENTACTION,和RESULT将被设置。然后,我希望能够将变量扩展后的字符串打印出来。是我唯一的选择eval吗?这似乎可行:

eval "echo ${str1}"

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

Answers:


23

使用您显示的输入类型,利用shell扩展将值替换为字符串的唯一方法eval是以某种形式使用。这是安全的,只要您控制的值即可,str1并且可以确保它仅引用被称为安全的变量(不包含机密数据)并且不包含任何其他未引用的shell特殊字符。您应该在双引号内或在here文档中扩展字符串,这种方式仅"$\`是特殊的(它们必须\在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调用,效果很好。
克莱顿·斯坦利

好的解决方案,这对我很有帮助。谢谢!
Stuart

8

当我理解您的意思时,我认为这些答案都不正确。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,即使已经调用过$RESULTvia,它parameter expansion也会被更改_top_of_script_pr(),大概是在运行时对其进行设置。起作用的原因是它_top_of_script_pr()是一个( subshelled )函数-我将其包含在其中,parens而不是{ curly braces }用于其他函数。因为它是在子locally scoped外壳中调用的,所以它设置的每个变量都是,当返回其父外壳时,这些值将消失。

但是当_more_important_function()设置$ACTIONglobally 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

此处文件

顺便说一下-空值或未设置变量的内联定义也是以下原因起作用的原因:

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

想到a的最佳方法here-document 是将实际文件流式传输到输入文件描述符。它们或多或少就是它们的本质,但是不同的外壳对它们的实现略有不同。

在任何情况下,如果不引号,就<<LIMITER可以将其流式传输进行评估。expansion.因此,在容器中声明变量是here-document可行的,但只有expansion这样,您才可以限制仅设置尚未设置的变量。尽管如此,这仍然完全符合您的描述需求,因为在调用模板打印功能时始终会设置默认值。

为什么不 eval?

好吧,我提供的示例提供了一种安全有效的接受方法,parameters.因为它可以处理范围,${parameter:=expansion}所以可以从外部定义via中的每个变量。因此,如果将所有这些放到名为template_pr.sh的脚本中并运行:

 % RESULT=something_else template_pr.sh

您会得到:

我去你母亲的家,在冰上看到迪士尼

如果你做书法,你会有所收获的

我去了公墓,看到冰上的迪士尼

如果您修数学,您将会有所成就

我去了公墓,看到冰上的迪士尼

如果您修数学,您将会有所成就

这对于在脚本中实际设置的变量不起作用,例如$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")。可以通过一些修改来解决此问题,或者仅通过对模板变量进行硬编码而不是动态修改即可。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.