如何在 Bash 中分配一个名称从另一个变量扩展 ($) 的变量?

如何在 Bash 中分配一个名称从另一个变量扩展 ($) 的变量?

我有如下的一部分脚本。

notify() {
printf -v "old_notif" "%d" "$notif_$2"
now=$(date +%s)
fark=$((now - old_notif))
echo $old_notif
if [ -z $old_notif ]; then  

.....

x1=$(date +%s)
export "notif_$2=$x1"

我使用 2 个参数调用通知。

notify xyz klm

我在带有导出的函数中创建了一个动态变量。主脚本处于while循环中。我的问题是如何在检查notif_$2中使用变量if?或者如何将其十进制内容分配给另一个变量?在我尝试的示例中,printf它没有分配内容。

答案1

总结:一种方法是local tmp="notif_$2"; printf -v "old_notif" "%d" "${!tmp}"

您正在尝试扩展范围(在本例中为变量),其名称本身必须通过扩展另一个参数(在本例中为位置参数)来获取。此外,必须扩展位置参数,然后将其与文本连接起来notif_,以生成必须扩展的文本。

Bash 可以让你干净利落地完成这个间接扩张名称引用

本文的前两部分展示了使用这些技术对命令的直接替换printf -v。其余部分是可选的;它们进一步解释了这些功能以及您可以使用它们做什么。

方式一:间接扩张

间接扩张从概念上讲,与您所写的内容最为相似。除了printf -v现在的命令,您还可以使用以下两个命令:

local tmp="notif_$2"
printf -v "old_notif" "%d" "${!tmp}"

local使tmp参数成为 shell 函数的本地参数。如果出于某种原因您不想这样,只需省略单词local。该参数不需要被称为tmp

间接扩展涵盖3.5.3 Shell参数扩展在里面Bash 参考手册

方法 2:Namerefs

另一种方法是使用一个名称引用。nameref 引用一个参数。你可以通过参数的名称来创建它,但是一旦创建,当你读取或写入它时,它的行为就像是那个参数一样。

要使用 nameref,请将printf -v命令替换为:

local -n ref="notif_$2"
printf -v "old_notif" "%d" "$ref"

将选项传递-nlocaldeclare会导致新引入的参数成为 nameref。请注意,在printf命令中,您将使用普通参数扩展 ( $ref) — — 而不是间接扩展 — — 因为 shell 会自动为 nameref 执行间接操作。

这里确实需要local或,因为单独使用会出错,而且单独使用不会产生 nameref。在 shell 函数中,没有选项的行为类似于,因此,如果您愿意,可以使用该样式。或者,在特殊情况下,如果您希望 nameref 在函数返回后可用,则可以使用declare-n ref="notif_$2"ref="notif_$2"declare-glocaldeclare -g 谢谢G-Man为了指出这个错误在此答案的早期版本中。

(根据您的需要,使用 nameref 甚至可能不仅仅用于这两行。要了解如何操作,请继续阅读。)

Nameref 涵盖在3.4 Shell 参数在里面Bash 参考手册

详细说明:间接扩展的工作原理

Shell 特性最容易通过交互式使用来演示,但是位置参数(比如2,通过 扩展$2)在 shell 函数之外的含义并不完全相同,而local内置函数在函数之外根本不起作用(你可以使用declare它,或者什么都不用)。

因此,从一个简单的例子开始,假设你在 shell 中以交互方式工作,并且已经运行:

x=foo
export "notif_$x=1234"

运行该程序后,1234它将存储在参数中notif_foo。(它还将该参数导出为环境变量。)我们可以通过检查来看到这一点notif_foo

echo "$notif_foo"

这将输出1234

你的情况类似于不知道什么x参数中。(在您的例子中,您不知道传递给 shell 函数的第二个位置参数中的内容。我很快就会讲到。)

但是您可以构建参数名称并将其放在另一个参数中:

y="notif_$x"

现在y成立notif_foo,因此您可以对使用间接扩展y,如下所示:

"${!y}"

这会扩展为1234,就像您使用 一样"$notif_foo"。但您不需要知道$xfoo可以使用它。

例如,这将分配1234old_notif

old_notif="${!y}"

如果您需要格式化 的内容$notif_foo,也可以这样做。例如,printf如果需要,您可以使用。此命令类似于您问题中的命令,并具有分配给printf的效果:1234old_notif

printf -v old_notif '%d' "${!y}"

(它也适用于您原来的引用风格,即printf -v "old_notif" "%d" "${!y}"具有相同的效果。)

当然,这依赖于y首先适当分配的参数。

要编写自己的 shell 函数,您需要$x用替换$2,并且可能需要使用local内置函数来防止临时变量(我现在将调用 来代替tmpy泄漏出函数的范围。

local tmp="notif_$2"
printf -v old_notif '%d' "${!tmp}"

或者,使用您在问题中使用的引用样式:

local tmp="notif_$2"
printf -v "old_notif" "%d" "${!tmp}"

详细说明:Namerefs 如何工作

要以交互方式尝试 namrefs,您必须使用declare -n而不是local -n(因为local仅在 shell 函数体内有效或需要)。

与前面一样,假设您已经运行:

x=foo
export "notif_$x=1234"

因此$notif_foo扩展为1234。您可以创建一个 nameref ,指向由扩展的结果命名的参数"notif_$x"

declare -n y="notif_$x"

现在y指的是名称notif_foo,并且普通的参数扩展y会自动取消引用该名称,从而扩展参数notif_foo。也就是说,这将扩展为1234,就像您使用了 一样$notif_foo

"$y"

要编写 shell 函数,您将$x用替换$2,我建议使用local而不是declare。我还建议使用比 更具描述性的名称y;对于没有其他 nameref 声明的短函数,ref可能足够清楚。

local -n ref="notif_$2"
print -v old_notif '%d' "$ref"

或者,使用你一直使用的引用样式:

local -n ref="notif_$2"
print -v "old_notif" "%d" "$ref"

Nameref 非常强大

nameref 可以让你对其引用的参数执行更多操作,而不仅仅是读取它。例如,你还可以写入它:

x=foo
declare -n y="notif_$x"
y=1234

第二条命令为可能尚不存在的参数创建 nameref。没问题!它将在您第一次赋值时创建,即使该赋值是通过名称引用。

第三条命令看起来就像它分配1234y,但实际上它将其分配给notif_foo。现在$y和都$notif_foo扩展为1234$notif_foo扩展为1234因为这是存储在 中的值notif_foo$y扩展为1234因为y是 的名称引用notif_foo

假设你知道什么y引用了。也就是说,假设你想从 中得到notif_foo,而不是。好吧,你可以,因为使用 nameref ,间接扩展会产生与其通常效果相反的效果。这将扩展为:1234ynotif_foo

"${!y}"

在您的函数中,您可以notif_$2通过 nameref 进行引入。

这表明了在您的函数中处理它的另一种方法notif_$2:您可以通过 nameref 来引入它,并在每次后续访问中使用该 nameref。

目前您有:

x1=$(date +%s)
export "notif_$2=$x1"

或者,您可以使用:

x1=$(date +%s)
local -n ref="notif_$2"
ref="$x1"
export "${!ref}=$ref"

但是,这比必要的更复杂,因为大概您只创建了参数,x1因为export "notif_$2=$(date +%s)"很难阅读。ref="$(date +%s)"但是,同样简单,因此您可以省略该x1=行并写入:

local -n ref="notif_$2"
ref="$(date +%s)"
export "${!ref}=$ref"

我突然想到,您可能export只是用来分配 shell 中使用的参数。如果您实际上不需要将其导出到子进程,那么您只需使用前两行,这比您拥有的更简单。

如果你需要导出,请使用所有三个。这仍然比你拥有的要复杂一些……但它可能会让你简化其余的部分你的功能,因为之后:

  • 您无需书写notif_$2,只需书写即可ref
  • 这种方法在书写不充分的情况下也能奏效notif_$2。也就是说,不再需要做任何特殊的事情(如上面的方法 1 和方法 2)来扩展名称为扩展 的结果的参数notif_$2。只需写入 即可ref
  • 这甚至适用于写入——以及创建和取消设置——参数。(在声明点之后,写入甚至可以创建引用的参数;取消设置引用的参数。)ref=textunset ref

如果要在整个函数中使用 nameref,则可能需要为其想一个比 更有意义的名称ref。 (当然,最好的名称将由您编写函数执行的任务决定。)

相关内容