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

Emr*_*nan 4 bash

我有一部分脚本如下。

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

.....

x1=$(date +%s)
export "notif_$2=$x1"
Run Code Online (Sandbox Code Playgroud)

我用 2 个参数调用通知。

notify xyz klm
Run Code Online (Sandbox Code Playgroud)

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

Eli*_*gan 9

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

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

Bash 允许您通过间接扩展nameref干净利落地做到这一点

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

方式一:间接扩张

从概念上讲,间接扩展与您所写的内容最相似。printf -v您可以使用以下两个命令,而不是您现在拥有的命令:

local tmp="notif_$2"
printf -v "old_notif" "%d" "${!tmp}"
Run Code Online (Sandbox Code Playgroud)

local使tmp参数局部于 shell 函数。如果由于某种原因你不想要那个,只需省略这个词local。该参数不需要被调用tmp

Bash 参考手册中的3.5.3 Shell Parameter Expansion中介绍了间接扩展。

方式 2:Namerefs

另一种方法是使用nameref。nameref 引用一个参数。您可以根据参数的名称创建它,但是一旦创建,当您读取或写入参数时,它的行为就好像它是该参数一样。

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

local -n ref="notif_$2"
printf -v "old_notif" "%d" "$ref"
Run Code Online (Sandbox Code Playgroud)

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

你确实需要localordeclare在这里,因为-n ref="notif_$2"它本身就是一个错误,ref="notif_$2"它本身不会产生 nameref。declare如果没有-gshell 函数中的选项,则行为类似于local,因此您可以根据需要使用该样式。或者,在你想要的nameref是使用返回的功能后,您可以使用不同寻常的情况下,declare -g 由于G-人指出一个关于这个错误在这个答案的早期版本。

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

Namerefs在Bash 参考手册的3.4 Shell 参数中有介绍。

详细说明:间接膨胀的工作原理

Shell 功能最容易通过交互使用来演示,但是位置参数(例如2,扩展通过$2)在 shell 函数之外没有完全相同的含义,并且local内置函数在函数之外根本不起作用(您可以declare改为使用,或者什么都没有)。

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

x=foo
export "notif_$x=1234"
Run Code Online (Sandbox Code Playgroud)

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

echo "$notif_foo"
Run Code Online (Sandbox Code Playgroud)

这输出1234.

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

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

y="notif_$x"
Run Code Online (Sandbox Code Playgroud)

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

"${!y}"
Run Code Online (Sandbox Code Playgroud)

这扩展为1234,就像您使用过"$notif_foo". 但是你不需要知道$xfoo使用它。

例如,这将分配1234old_notif

old_notif="${!y}"
Run Code Online (Sandbox Code Playgroud)

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

printf -v old_notif '%d' "${!y}"
Run Code Online (Sandbox Code Playgroud)

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

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

要编写您的 shell 函数,您将替换$x$2,并且您可能希望使用local内置函数来防止您的临时变量——我现在将调用它tmp而不是y——泄漏到函数的范围之外。

local tmp="notif_$2"
printf -v old_notif '%d' "${!tmp}"
Run Code Online (Sandbox Code Playgroud)

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

local tmp="notif_$2"
printf -v "old_notif" "%d" "${!tmp}"
Run Code Online (Sandbox Code Playgroud)

详细解释:Namerefs 是如何工作的

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

和以前一样,假设你已经运行:

x=foo
export "notif_$x=1234"
Run Code Online (Sandbox Code Playgroud)

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

declare -n y="notif_$x"
Run Code Online (Sandbox Code Playgroud)

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

"$y"
Run Code Online (Sandbox Code Playgroud)

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

local -n ref="notif_$2"
print -v old_notif '%d' "$ref"
Run Code Online (Sandbox Code Playgroud)

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

local -n ref="notif_$2"
print -v "old_notif" "%d" "$ref"
Run Code Online (Sandbox Code Playgroud)

Namerefs 很强大

nameref 使您可以使用它的被引用参数做更多的事情,而不仅仅是从中读取。例如,您还可以写入:

x=foo
declare -n y="notif_$x"
y=1234
Run Code Online (Sandbox Code Playgroud)

第二个命令为可能尚不存在的参数创建名称引用。那没问题!它将在您第一次分配给它时创建,即使该分配是通过nameref 进行的。

第三个命令看起来像是分配1234y,但实际上是将其分配给notif_foo。现在,这两个$y$notif_foo扩大到1234$notif_foo扩展为1234因为这是存储在notif_foo; $y扩展为1234因为ynotif_foo.

不过,假设您知道y指的是什么。也就是说,假设你想获得notif_foo,而不是1234y。嗯,你可以,因为使用 nameref,间接扩展与它通常的效果相反。这扩展为notif_foo

"${!y}"
Run Code Online (Sandbox Code Playgroud)

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

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

目前你有:

x1=$(date +%s)
export "notif_$2=$x1"
Run Code Online (Sandbox Code Playgroud)

作为替代方案,您可以使用:

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

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

local -n ref="notif_$2"
ref="$(date +%s)"
export "${!ref}=$ref"
Run Code Online (Sandbox Code Playgroud)

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

如果您确实需要导出它,请使用所有三个。它仍然比您拥有的要复杂一些......但它可以让您简化其余功能,因为之后:

  • 而不是必须写notif_$2,你可以只写ref
  • 即使在写作notif_$2不足的情况下,这也有效。也就是说,不再需要做任何特殊的事情(如上面的方式 1 和方式 2)来扩展名称为扩展结果的参数notif_$2。只是写ref
  • 这甚至适用于写入 -- 以及创建和取消设置 -- 参数。(在声明点之后,写入甚至可以创建被引用参数;取消设置被引用参数。)ref=textunset ref

如果您打算在整个函数中使用 nameref,您可能需要为它想一个比 .name 更有意义的名称ref。(当然,最好的名称取决于您编写函数要执行的任务。)