如何推迟变量扩展

Aar*_*ron 23 shell bash string

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

str1='I went to ${PLACE} and saw ${EVENT}'
str2='If you do ${ACTION} you will ${RESULT}'
Run Code Online (Sandbox Code Playgroud)

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

eval "echo ${str1}"
Run Code Online (Sandbox Code Playgroud)

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

Gil*_*il' 26

对于您显示的输入类型,利用 shell 扩展将值替换为字符串的唯一方法eval是以某种形式使用。只要您控制 的值str1并且可以确保它仅引用已知为安全的变量(不包含机密数据)并且不包含任何其他未加引号的 shell 特殊字符,这就是安全的。您应该在双引号内或 here 文档中扩展字符串,这种方式只是"$\`特殊的(它们需要以\in开头str1)。

eval "substituted=\"$str1\""
Run Code Online (Sandbox Code Playgroud)

定义一个函数而不是一个字符串会更健壮。

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

设置变量然后调用函数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\"."
Run Code Online (Sandbox Code Playgroud)

  • 使用函数来延迟评估并避免显式 eval 调用的出色工作。 (2认同)

mik*_*erv 10

当我理解你的意思时,我不相信这些答案中的任何一个是正确的。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
Run Code Online (Sandbox Code Playgroud)

中间

这是您定义其他函数以根据其结果调用您的打印函数的地方...

    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
    }
Run Code Online (Sandbox Code Playgroud)

底部

您现在已经完成了所有设置,因此您将在此处执行并提取结果。

    _less_important_function
    : "${PLACE:="the cemetery"}" 
    _more_important_function
    : "${RESULT:="regret it."}" 
    _less_important_function    
Run Code Online (Sandbox Code Playgroud)

结果

稍后我将讨论原因,但运行上述代码会产生以下结果:

_less_important_function()'s 第一次运行:

我去你妈妈家看了冰上迪斯尼。

如果你会写书法,你就会成功。

然后 _more_important_function():

我去了墓地,看到了冰上迪斯尼。

如果你做补习数学,你就会成功。

_less_important_function() 再次:

我去了墓地,看到了冰上迪斯尼。

如果你做补习数学,你会后悔的。

这个怎么运作:

这里的关键特性是conditional ${parameter} expansion.您可以使用以下形式将变量设置为仅当变量未设置或为空的值的概念:

${var_name:=desired_value}

相反,如果您只想设置一个未设置的变量,则可以省略:colon并且空值将保持原样。

范围:

您可能会注意到,在上面的示例中$PLACE,即使已经调用了$RESULTset via 时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}我们会留下它的值 - 外壳会尝试在线执行。如果它试图运行the cemetery它只会向你吐出一些错误。PLACE="${PLACE:="the cemetery"}"会产生相同的结果,但在这种情况下它也是多余的,我更喜欢 shell: ${did:=nothing, gracefully}.

它确实允许您这样做:

    echo ${var:=something or other}
    echo $var
something or other
something or other
Run Code Online (Sandbox Code Playgroud)

这里-文档

顺便说一句 - null 或 unset 变量的内联定义也是以下工作的原因:

    <<HEREDOC echo $yo
        ${yo=yoyo}
    HEREDOC
yoyo
Run Code Online (Sandbox Code Playgroud)

将 ahere-document 视为流到输入文件描述符的实际文件的最佳方式。它们或多或少就是这样,但是不同的 shell 实现它们的方式略有不同。

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

为什么不 eval?

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

 % RESULT=something_else template_pr.sh
Run Code Online (Sandbox Code Playgroud)

你会得到:

我去你妈妈家看了冰上迪斯尼

如果你会写书法,你会有所作为_else

我去了墓地,看到了冰上迪斯尼

如果你做补习数学,你会的东西_else

我去了墓地,看到了冰上迪斯尼

如果你做补习数学,你会的东西_else

这对于那些在脚本中逐字设置的变量不起作用,例如$EVENT, $ACTION,$one,但我只是以这种方式定义了它们来演示差异。

在任何情况下,接受未知输入到evaled语句中本质上是不安全的,而它parameter expansion是专门为这样做而设计的。