在阅读了 ilkkachu 对这个问题的回答后,我了解到内置的declare
(带参数)shell 的存在。-n
help declare
带来:
设置变量值和属性。
声明变量并赋予它们属性。如果没有给出名称,则显示所有变量的属性和值。
-n ... 使 NAME 成为对其值命名的变量的引用
我要求用一个例子进行一般性解释,declare
因为我不明白man
. 我知道什么是变量并对其进行扩展,但我仍然想念man
on declare
(变量属性?)。
也许您想根据 ilkkachu 在答案中的代码来解释这一点:
#!/bin/bash
function read_and_verify {
read -p "Please enter value for '$1': " tmp1
read -p "Please repeat the value to verify: " tmp2
if [ "$tmp1" != "$tmp2" ]; then
echo "Values unmatched. Please try again."; return 2
else
declare -n ref="$1"
ref=$tmp1
fi
}
在大多数情况下,使用隐式声明就足够了
bash
但是,有时您希望变量的值仅为整数(因此,如果以后它会更改,即使是自动更改,也只能更改为整数,在某些情况下默认为零),并且可以使用:
或者
有时你想要数组,然后你需要
declare
或者
You can find good tutorials about arrays in
bash
when you browse the internet with the search string 'bash array tutorial' (without quotes), for examplelinuxconfig.org/how-to-use-arrays-in-bash-script
I think these are the most common cases when you declare variables.
Please notice also, that
declare
makes the variable local (in the function)without any name, it lists all variables (in the active shell)
Finally, you get a brief summary of the features of the shell built-in command
declare
inbash
with the command的输出
help declare
非常简洁。可以在man bash
或中找到更清晰的解释info bash
——后者是以下内容的来源。首先,一些定义。关于变量和属性:
关于
declare
内置:请注意,名称引用变量仅在 Bash 4.3 或更高版本1中可用。
此外,为了对 Bash 中的变量属性进行有用的介绍,
declare
我会向您指出“做什么和做什么?”的答案(不过,它主要关注变量的范围)。declare name
declare -g
基本上2,
declare name=[value]
相当于name=[value]
您可能熟悉的作业。在这两种情况下,如果缺少,name
则分配空值。value
请注意,略有不同的
declare name
是,它不会设置变量name
3:因此,变量
name
可以是:declare name
name=
or之后declare name=
;name=value
declare name=value
更普遍,
declare [options] name=value
name
——这是一个有名字的参数,而它又只是你可以用来存储信息的内存的一部分4;value
;name
的属性,这些属性定义了它可以存储的值的种类(严格来说,不是类型,因为 Bash 的语言没有类型化)以及可以操作的方式。用一个例子可能更容易解释属性: using
declare -i name
将设置 的“整数”属性name
,让它被视为整数;引用手册,“当变量被赋值时将执行算术评估”:鉴于上述情况,ilkkachu 的代码中发生的情况是:
声明了一个名为的变量
ref
,并设置了“nameref”属性,并将$1
(第一个位置参数)的内容分配给它:名称引用变量的目的
ref
是保存另一个变量的名称,通常不会事先知道,可能是因为我们希望它是动态定义的(例如,因为我们想重用一段代码并拥有它应用于多个变量),并提供一种方便的方式来引用(和操作)它。(但不是唯一的:间接是一种替代方法;请参阅Shell Parameter Expansion)。当变量的值
tmp1
被赋值给ref
:ref
隐式声明了一个附加变量,其名称为 的值。的值tmp1
也通过对 的显式赋值间接地赋值给隐式声明的变量ref
。在您的链接问题的上下文中,调用
read_and_verify
为将声明变量
domain
并为其赋值tmp1
(即用户的输入)。它的设计目的是重用与用户交互的代码,并利用 nameref 变量来声明domain
和一些其他变量。为了仔细研究隐式部分,我们可以逐步重现该过程:
1参考:CHANGES文件,“3. Bash 中的新功能”部分,点“w”。
这可能是相关的:例如,CentOS Linux 7.6(目前是最新版本)随 Bash 4.2 一起提供。
2与 shell 内置函数一样,详尽而简明的解释是难以捉摸的,因为它们执行各种不同的、可能是异构的操作。我将只专注于声明、分配和设置属性,并且我会考虑列出、确定范围和删除属性,这超出了这个答案的范围。
3此行为
declare -p
已在 Bash 4.4 中引入。参考:CHANGES文件,“3. Bash 中的新功能”部分,点“f”。正如G-Man在评论中指出的那样,在 Bash 4.3 中会
declare name; declare -p name
产生错误。但是您仍然可以name
使用declare -p | grep 'declare -- name'
.4 FullBashGuide,mywiki.wooledge.org上的参数
我会尽力解释这一点,但如果我不遵循您提供的示例,请原谅我。我宁愿尝试用我自己的、不同的方法来引导你。
您说您已经了解诸如“变量”和“扩展它们”等概念,所以我将略读一些需要更深入关注的背景知识。
所以我首先要说的是,在最基本的层面上,
declare
命令只是一种告诉 Bash 你需要一个变量值(即在脚本执行期间可能会改变的值)的一种方式,你将参考该值使用特定名称,正是您在declare
命令本身旁边指示的名称。那是:
告诉 Bash 您希望名为的变量
foo
具有值bar
。但是.. 等一下.. 我们可以不使用就做到这一点,不是吗
declare
。如:非常真实。
好吧,碰巧上面的简单赋值实际上是一种隐含的方式……事实上……声明一个变量。
(也碰巧上面是改变变量named值的几种方法之一;确实,它恰恰是最直接、简洁、明显、直接的方法……但它不是唯一的……。 .我稍后会回到这个..
foo
)。但是,如果完全有可能声明一个“将标记变量值的名称”(为了简洁起见,以后只是“变量”)而不使用
declare
,你为什么要使用这个浮夸的“声明” “ 命令 ?答案在于上述隐式声明变量
foo="bar"
(这种类型是字符串类型,即没有特定含义的字符序列。因此,当您使用隐式声明时,您会得到一个字符串。
但是,作为程序员,您有时需要将变量视为,例如,一个数字.. 您需要在其上进行算术运算.. 并且使用隐式声明
foo=5+6
不会使 Bash 将值 11 分配给foo
您可能预计。它宁愿分配给foo
三个字符的序列5
+
6
。所以..您需要一种方法来告诉 Bash 您想
foo
被视为一个数字,而不是一个字符串.. 这就是显式declare
的有用之处。说啊:
Bash 会很乐意为您计算,并将数值11 分配给 variable
foo
。那就是:通过说
declare -i foo
你给变量一个整数foo
的属性。声明数字(确切地说是整数,因为 Bash 仍然不理解小数、浮点等)可能是使用 的第一个原因
declare
,但这不是唯一的原因。正如您已经了解的那样,您可以为变量赋予一些其他属性。例如,您可以让 Bash 始终将变量的值设为大写:如果您说declare -u foo
,那么从那时起,当您说foo=bar
Bash 实际上将字符串分配给BAR
变量时foo
。为了将这些属性中的任何一个赋予变量,您必须使用
declare
命令,别无选择。现在,您可以提供的另一个属性
declare
是臭名昭著的“name-ref”-n
属性。(现在我要恢复我之前搁置的概念)。name-ref 属性基本上允许 Bash 程序员以另一种方式更改变量的值。更准确地说,它提供了一种间接的方式来做到这一点。
以下是它的工作原理:
您
declare
是一个具有该-n
属性的变量,非常建议(尽管不是严格要求,但它使事情变得更简单)您也可以在同一命令中为这个变量赋值。declare
像这样:这告诉 Bash,从那时起,每次您将使用或更改变量 named 的值时
baz
,它都将实际使用或更改变量 named 的值foo
。这意味着,从那时起,你可以说类似
baz=10+3
makefoo
得到 13 的值。当然假设foo
之前声明为整数 (declare -i
),就像我们在一分钟前所做的那样,否则它将得到四个的序列字符1
0
+
3
。另外:如果你
foo
直接改变 's 的值,如foo=15
,你会看到 15 也说echo “${baz}”
。这是因为baz
声明为 name-ref 的变量foo
总是反映foo
's 的值。上面的
declare -n
命令被称为“名称引用”,因为它使变量baz
引用另一个变量的名称。事实上,我们已经声明baz
了具有值“foo”,由于该-n
选项,它被 Bash 处理为另一个变量的名称。现在,你到底为什么要这样做?
嗯.. 值得一提的是,这是一个满足高级需求的功能。
事实上,当程序员面临一个真正需要名称引用的问题时,这种问题很可能应该通过使用适当的编程语言而不是 Bash 来解决。
其中一个高级需求是,例如,当您作为程序员在开发过程中无法知道在脚本的特定点中必须使用哪个变量时,但它会在运行时动态地完全知道。鉴于任何程序员都无法在运行时进行干预,唯一的选择是 事先在脚本中为这种情况做好准备,而“name-ref”可能是唯一可行的方法。例如,作为这种高级需求的广为人知的用例,想想插件。“可插件化”程序的程序员需要事先为未来的(可能是第三方的)插件提供通用条款。因此,程序员将需要在 Bash 中使用诸如 name-ref 之类的工具。
另一个高级需求是当您必须处理RAM 中的大量数据并且您还需要在脚本的函数周围传递这些数据时,这些函数还必须在此过程中修改这些数据。在这种情况下,您当然可以将数据从一个函数复制到另一个函数(就像 Bash 在您这样做
dest_var="${src_var}"
或调用 in 之类的函数时所做的那样myfunc "${src_var}"
),但是作为大量数据,它会造成大量 RAM 浪费和非常低效运作。因此,如果出现这种情况,解决方案不是使用数据的副本,而是使用参考到那个数据。在 Bash 中,一个 name-ref。这个用例确实是任何现代编程语言的规范,但对于 Bash 来说却是非常特殊的,因为 Bash 主要是为处理文件和外部命令的简短脚本而设计的,因此 Bash 脚本很少需要通过巨大的函数之间的数据量。当脚本的函数确实需要共享一些数据(访问并修改它)时,通常只需使用全局变量即可实现,这在 Bash 脚本中很常见,但在适当的编程语言中却非常不推荐使用。然后,在 Bash 中,name-refs 可能有一个值得注意的用例,并且(可能具有讽刺意味)它与您使用其他类型的变量相关联:
declare -a
)declare -A
)。这些变量类型可以通过使用 name-refs 而不是通过正常复制更容易(也更有效地)传递函数,即使它们不携带大量数据。
如果所有这些例子听起来很奇怪,而且仍然难以理解,那只是因为 name-refs 确实是一个高级主题,并且很少需要 Bash 的典型使用场景。
我可以告诉你一些我发现在 Bash 中使用 name-refs 的场合,但到目前为止,它们主要用于非常“深奥”和复杂的需求,我担心如果我描述它们,我只会在你学习的这一点上,让你的事情变得复杂。仅提及最不复杂(并且可能不是深奥的):从函数返回值。Bash 并不真正支持此功能,因此我通过使用 name-refs 获得了相同的功能。顺便说一句,这正是您的示例代码所做的。
除此之外,还有一点个人建议,实际上更适合发表评论,但我无法将其浓缩到足以适应 StackExchange 评论的限制。
我认为您目前应该做的最多的事情就是通过使用我展示的简单示例以及您提供的示例代码来试验名称引用,暂时忽略“为什么”部分,只关注“它是如何工作的”部分。通过一些实验,“如何”部分可能会更好地进入你的脑海,这样当你(或如果)你遇到一个真正的实际问题时,“为什么”部分会在适当的时候让你清楚地知道—— ref 真的会派上用场。
通常,
declare
在bash
shell 中设置(或删除或显示)变量的属性。属性是一种注释,它说“这是一个名称引用”,或者“这是一个关联数组”,或者“这个变量应该总是被评估为一个整数”,或者“这个变量是只读的,不能重新设置”,或“此变量已导出(环境变量)”等。built-in 是 in的
typeset
同义词,在declare
其他shell(例如,它的起源地和)中用于设置变量属性。bash
typeset
ksh
zsh
更仔细地查看问题中的名称参考示例:
您显示的 shell 函数,以及使用它的附加代码:
运行这个:
这表明当用户输入两个不同的字符串
foo
时,变量没有被设置为任何值。这表明该变量被设置为用户在两次输入相同
foo
字符串时输入的字符串。在脚本的主要部分中
$foo
获取值的方法是通过 shell 函数中的以下行:hello
其中
$tmp1
是hello
用户输入的字符串,并且$1
是foo
从脚本的主要部分通过函数命令行传入的字符串。请注意,该
ref
变量被声明declare -n
为名称引用变量,并且该值foo
作为该声明中的值给出。这意味着从那时起,直到变量超出范围,对变量的任何使用ref
都将与 using 相同foo
。该变量ref
是此时引用的名称引用变量foo
。这会导致为 赋值
ref
,就像在声明后面的行中所做的那样,会将值赋值给foo
。然后该值在脚本的主要部分中
hello
可用。$foo