【问题标题】:In Bash, is there a way to expand variables twice in double quotes?在 Bash 中,有没有办法在双引号中将变量扩展两次?
【发布时间】:2023-04-06 12:25:01
【问题描述】:

为了调试我的脚本,我想在每个输出的开头添加内部变量 $FUNCNAME 和 $LINENO,这样我就知道输出发生在哪个函数和行号上。

foo(){
    local bar="something"
    echo "$FUNCNAME $LINENO: I just set bar to $bar"
}

但是由于会有很多调试输出,如果我可以执行以下操作会更简洁:

foo(){
    local trace='$FUNCNAME $LINENO'
    local bar="something"
    echo "$trace: I just set bar to $bar"
}

但上面的字面意思是: “$FUNCNAME $LINENO:我刚刚设置了一些东西” 我认为这样做是因为双引号仅在内部扩展变量一次。

有没有一种语法简洁的方法可以在同一行中将变量扩展两次?

【问题讨论】:

  • 哎呀。如果您的变量中只有一个变量名,那将很容易——在这种情况下,它只是标准的间接扩展。相比之下,如果你想用任意数量的变量扩展一个模板,你就会进入eval 空间,这显然是不安全的。
  • this 几乎可以工作 - 如果local trace=FUNCNAME 那么你可以写echo ${!trace} ": I just set bar to $bar"
  • @codeforester ...这是一个相当严厉的标题编辑。从我的(原始)答案 f/e 顶部的单行摘要中完全删除上下文,并适用于不解决 OP 关于扩展的直接/直接问题的答案根本 .
  • 我也是这么想的……既然你这么说,我就回滚了。

标签: linux bash parameter-passing variable-expansion parameter-expansion


【解决方案1】:

在处理运行时数据时,您不能安全地对扩展进行两次评估。

有进行重新评估的方法,但它们需要信任您的数据——在 NSA 系统设计的意义上:“受信任的组件是在系统发生故障时会破坏您的系统的组件”。

有关详细讨论,请参阅BashFAQ #48。请记住,如果您可以记录文件名,则 除 NUL 之外的任何字符 都可以出现在 UNIX 文件名中。 $(rm -rf ~)'$(rm -rf ~)'.txt 是合法名称。 * 是合法名称。

考虑另一种方法:

#!/usr/bin/env bash

trace() { echo "${FUNCNAME[1]}:${BASH_LINENO[0]}: $*" >&2; }

foo() {
        bar=baz
        trace "I just set bar to $bar"
}

foo

...当使用 bash 4.4.19(1)-release 运行时,会发出:

foo:7: I just set bar to baz

注意${BASH_LINENO[0]}${FUNCNAME[1]}的使用;这是因为BASH_LINENO 定义如下:

一个数组变量,其成员是调用 FUNCNAME 的每个相应成员的源文件中的行号。

因此,FUNCNAME[0]trace,而 FUNCNAME[1]foo;而BASH_LINENO[0] 是调用trace 的那一行——该行位于函数foo 内。

【讨论】:

    【解决方案2】:

    虽然eval 有它的危险,但它的作用是进行第二次扩展:

    foo(){
        local trace='$FUNCNAME $LINENO'
        local bar="something"
        eval echo "$trace: I just set bar to $bar"
    }
    
    foo
    

    给予:

    foo 6: I just set bar to something
    

    请注意不要eval 来自外部来源的任何内容,因为您可以将命令注入到字符串中。

    【讨论】:

    • 不仅仅是命令注入风险——您也不能相信日志是准确的(因此,它对调试很有用)。如果您设置bar='*' 而不是bar=something,则引用将无法保留到echo,因此您将在日志中获得文件名列表。
    • eval 'echo '"$trace"'": I just set bar to $bar"' 会更安全(只扩展$trace,而不是字符串的其余部分,两次),但显然有一个易用性命中。
    • @CharlesDuffy:接受。这个用例似乎是 eval 最初设计的目的,我觉得 someone 应该将其作为解决方案展示。
    • 远比"*"差的是";rm -irf /"
    • 我建议如果有人可以破解一些东西来注入该命令,那么你就会遇到更大的问题。
    【解决方案3】:

    双扩展;但是,它不会如你所愿。

    是的,bash 提供了一种对变量进行“双重扩展”的方法,也就是一种首先解释变量,然后将其作为其他变量的名称的方法,其中另一个变量是实际要扩展的内容。这被称为“间接”。使用“间接”,bash 允许一个 shell 变量引用另一个 shell 变量,最终值来自被引用的变量。因此,bash 变量可以通过引用传递。

    语法只是普通的大括号样式扩展,但名称前有一个感叹号。

    ${!VARNAME}
    

    它是这样使用的:

    BAR="my final value";
    FOO=BAR
    echo ${!FOO};
    

    ...产生这个输出...

    my final value
    

    ,您不能使用此机制来执行与 $( eval "echo $VAR1 $VAR2" ) 相同的操作。第一次解释的结果必须是一个 shell 变量的名字。它不接受字符串,也不理解美元符号。所以这行不通:

    BAR="my final value";
    FOO='$BAR'; # The dollar sign confuses things
    echo ${!FOO}; # Fails because there is no variable named '$BAR'
    

    所以,它没有解决你的终极任务。尽管如此,间接寻址还是一个强大的工具。

    【讨论】:

    • 我们有很多关于如何使用间接的预先存在的 SO 问题——Dynamic variable names in bash 可能是最规范的。 BashFAQ #6 是另一个很棒的资源。综上所述,如果我相信这里的 OP 会就这个主题提出问题,我会以重复的形式结束这个问题,而不是回答它。
    猜你喜欢
    • 2012-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-19
    • 1970-01-01
    • 2023-01-08
    • 2016-08-26
    • 2021-02-17
    相关资源
    最近更新 更多