Bash 中复合变量的延迟扩展

Delayed expansion of composite variable in Bash

我将一个变量定义为其他变量和一些文本的组合,并且我试图让这个变量在赋值时不扩展它包含的变量。但我希望它在以后调用时扩展。这样我就可以重复使用相同的模板来打印不同的结果,因为内部变量不断变化。我正在努力避免 eval 尽可能多,因为我将从第三方收到一些内部变量,我不知道会发生什么。

我的用例,如下所示,是有一些“调用堆栈”,这样我就可以用相同的格式记录所有消息,并以某种格式记录脚本、函数和记录消息的行,比如这个:script.sh:this_function:42.

我尝试的解决方案

called.sh:

#!/bin/bash

SCRIPT_NAME="`basename "${BASH_SOURCE[0]}"`"
CURR_STACK="${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}"


echo "${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}"
echo "${CURR_STACK}"
echo

function _func_1 {
    echo "${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}"
    echo "${CURR_STACK}"
}
_func_1

因此,我打算在打印 "${CURR_STACK}" 时获得与打印上一行时相同的结果。

如果有一些内置的或其他聪明的方法来记录这个 'call stack',一定要告诉我!我很乐意挥手告别我的代码,但我仍然想知道如何防止变量在分配 CURR_STACK 时立即扩展,但仍能使它们能够进一步扩展。

我是不是漏掉了一些 shopt

我尝试过的:

案例1(在第4行展开):

CURR_STACK="${SCRIPT_NAME}:${FUNNAME[0]}:${LINENO[0]}"
CURR_STACK="`echo "${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}"`"
CURR_STACK="`echo "${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}"`"
called.sh::7              <------------------| These are control lines
called.sh::4  <---------------. .------------| With the results I expect to get.
                               X
called.sh:_func_1:12      <---´ `-------| Both indicate that the values expanded
called.sh::4  <-------------------------| on line 4 - when CURR_STACK was set.

情况2(完全不展开):

CURR_STACK="${SCRIPT_NAME}:${FUNNAME[0]}:${LINENO[0]}"
CURR_STACK=${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}
CURR_STACK="`echo '${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}'`"
called.sh::7
${SCRIPT_NAME}:${FUNNAME[0]}:${LINENO[0]}  <-------.----| No expansion at all!...
                                                  / 
called.sh::12                                    /
${SCRIPT_NAME}:${FUNNAME[0]}:${LINENO[0]}  <----´

你熟悉eval吗?

$ a=this; b=is; c=a; d=test;
$ e='echo "$a $b $c $d"';
$ eval $e;
this is a test

$ b='is NOT';  # modify one of the variables
$ eval $e;
this is NOT a test

$ f=$(eval $e);  # capture the value of the "eval" statement
$ echo $f;
this is NOT a test

Shell 变量存储的是纯惰性文本(*),不是可执行代码;这里实际上没有任何延迟评估的概念。要在使用时做一些事情,创建一个函数而不是变量:

print_curr_stack() {
    echo "$(basename "${BASH_SOURCE[1]}"):${FUNCNAME[1]}:${BASH_LINENO[0]}"
}
# ...
echo "We are now at $(print_curr_stack)"
# Or just run it directly:
print_curr_stack

注意:使用 BASH_SOURCE[1]FUNCNAME[1] 获取有关函数 运行 来自的上下文的信息,而不是它在函数本身中的位置。但出于某种原因我不清楚,BASH_LINENO[1] 得到了错误的信息,BASH_LINENO[0] 是你想要的。

您也可以编写它以允许调用者指定要打印的其他文本:

print_curr_stack() {
    echo "$@" "$(basename "${BASH_SOURCE[1]}"):${FUNCNAME[1]}:${BASH_LINENO[0]}"
}
# ...
print_curr_stack "We are now at"

(* 我所说的变量只包含惰性文本有一个例外:一些变量——如 $LINENO$RANDOM 等——由 shell 本身。但是你不能像这样创建新的,除非修改 shell 本身。)