24 bash function shell-builtin control-flow declare
在阅读了 ilkkachu 对这个问题的回答后,我了解到内置的declare
(带参数-n
)shell的存在。
help declare
带来:
设置变量值和属性。
声明变量并赋予它们属性。如果没有给出名称,则显示所有变量的属性和值。
-n ... 使 NAME 成为对其值命名的变量的引用
我要求用一个例子declare
做一个一般性的解释,因为我不理解man
. 我知道什么是变量和扩大,但我还是错过了man
上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
}
Run Code Online (Sandbox Code Playgroud)
fra*_*san 16
的输出help declare
非常简洁。可以在man bash
or 中找到更清晰的解释info bash
——后者是以下内容的来源。
首先,一些定义。关于变量和属性:
甲参数是存储的值的实体。...变量是由 a 表示的参数
name
。一个变量有一个值和零个或多个属性。使用declare
内置命令分配属性...
关于declare
内置:
declare
Run Code Online (Sandbox Code Playgroud)declare [-aAfFgilnrtux] [-p] [name[=value] …]
声明变量并赋予它们属性。如果没有给出名称,则显示变量的值。
...
-n
为每个名称指定nameref属性,使其成为对另一个变量的名称引用。另一个变量由name的值定义。对name 的所有引用、赋值和属性修改,除了使用或更改-n
属性本身的那些,都是在name值引用的变量上执行的。...
请注意,名称引用变量仅在 Bash 4.3 或更高版本1 中可用。
此外,为了declare
对 Bash 中的变量属性和变量属性进行有用的介绍,我会向您指出“做什么和做什么?”的答案(不过,它主要关注变量的范围)。declare name
declare -g
基本上2,declare name=[value]
相当于name=[value]
您可能熟悉的作业。在这两种情况下,name
如果value
缺失则分配空值。
请注意,稍微不同的declare name
不会设置变量name
3:
$ declare name
## With the -p option, declare is used to display
## attributes and values of variables
$ declare -p name
declare -- name ## "name" exists
## Parameter expansion can be used to reveal if a variable is set:
## "isunset" is substituted to "name" only if unset
$ echo "${name-isunset}"
isunset
Run Code Online (Sandbox Code Playgroud)
因此,变量name
可以是:
declare name
;name=
declare name=
name=value
declare name=value
更普遍, declare [options] name=value
name
——它是一个带有名称的参数,而它只是您可以用来存储信息的一部分内存4;value
;name
的属性,它定义了它可以存储的值的类型(严格来说不是根据类型,因为 Bash 的语言不是类型化的)和它可以被操纵的方式。用一个例子来解释属性可能更容易: usingdeclare -i name
将设置 的“整数”属性name
,让它被视为一个整数;引用手册,“当变量被赋值时将执行算术评估”:
## Let's compare an ordinary variable with an integer
$ declare var
$ declare -i int
$ var="1+1"
$ int="1+1"
$ echo "$var"
1+1 ## The literal "1+1"
$ echo "$int"
2 ## The result of the evaluation of 1+1
Run Code Online (Sandbox Code Playgroud)
鉴于上述情况,ilkkachu 的代码中发生的事情是:
ref
声明了一个名为name的变量,并设置了“nameref”属性,并将$1
(第一个位置参数)的内容分配给它:
declare -n ref="$1"
Run Code Online (Sandbox Code Playgroud)
名称引用变量的目的ref
是保存另一个变量的名称,这通常不会提前知道,可能是因为我们希望它是动态定义的(例如,因为我们想要重用一段代码并拥有它应用于多个变量),并提供一种方便的方式来引用(和操作)它。(虽然不是唯一的:间接是另一种选择;请参阅Shell 参数扩展)。
当变量的值tmp1
被赋值给ref
:
ref=$tmp1
Run Code Online (Sandbox Code Playgroud)
ref
隐式声明了一个附加变量,其名称是 的值。的值tmp1
也通过对 的显式赋值间接赋值给隐式声明的变量ref
。
在您链接的问题的上下文中,调用read_and_verify
为
read_and_verify domain "Prompt text here..."
Run Code Online (Sandbox Code Playgroud)
将声明变量domain
并为其赋值tmp1
(即用户的输入)。它完全旨在重用与用户交互的代码并利用 nameref 变量来声明domain
和一些其他变量。
要仔细查看隐式部分,我们可以逐步重现该过程:
## Assign a value to the first positional argument
$ set -- "domain"
## Declare the same "tmp1" variable as in your code
$ tmp1="value for domain"
## Declare a "ref" variable with the nameref attribute set and
## assign the value "domain" to it
$ declare -n ref="$1"
## Note that there is no "domain" variable yet
$ declare -p domain
bash: declare: domain: not found
## Assign a value to "ref" and, indirectly, to the "domain" variable
## that is implicitly declared
$ ref=$tmp1
## Verify that a variable named "domain" now exists, and that
## its value is that of "tmp1"
$ declare -p domain
declare -- domain="value for domain"
## Verify that "ref" is actually a reference to "domain"
$ domain="new value"
$ echo "$domain"
new value
$ declare -p ref
declare -n ref="domain"
$ echo "$ref"
new value
Run Code Online (Sandbox Code Playgroud)
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
sud*_*dus 15
在大多数情况下,在 bash
asdf="some text"
Run Code Online (Sandbox Code Playgroud)
但是,有时您希望变量的值只能是整数(因此,如果它以后会更改,即使是自动更改,它也只能更改为整数,在某些情况下默认为零),并且可以使用:
declare -i num
Run Code Online (Sandbox Code Playgroud)
或者
declare -i num=15
Run Code Online (Sandbox Code Playgroud)
有时你想要数组,然后你需要 declare
declare -a asdf # indexed type
Run Code Online (Sandbox Code Playgroud)
或者
declare -A asdf # associative type
Run Code Online (Sandbox Code Playgroud)
例如,bash
当您使用搜索字符串“bash array tutorial”(不带引号)浏览 Internet 时,可以找到有关数组的很好的教程
linuxconfig.org/how-to-use-arrays-in-bash-script
我认为这些是声明变量时最常见的情况。
另请注意,
declare
使变量局部(在函数中)没有任何名称,它列出了所有变量(在活动 shell 中)
declare
Run Code Online (Sandbox Code Playgroud)最后,您将在命令declare
中bash
获得shell 内置命令的功能的简要总结
help declare
Run Code Online (Sandbox Code Playgroud)
LL3*_*LL3 10
我会尝试解释这一点,但如果我不遵循您提供的示例,请原谅我。我宁愿尝试按照我自己的不同方法来指导您。
你说你已经理解了诸如“变量”和“扩展它们”等概念,所以我将略读一些背景知识,否则需要更深入地关注。
所以我首先要说的是,在最基本的层面上,该declare
命令只是告诉 Bash 您需要一个变量值(即在脚本执行期间可能会更改的值)的一种方式,并且您将参考该值使用特定名称,正是您在declare
命令本身旁边指示的名称。
那是:
declare foo="bar"
Run Code Online (Sandbox Code Playgroud)
告诉 Bash 您希望命名的变量foo
具有 value bar
。
但是.. 等一下.. 我们可以不使用就做到这一点,不是吗declare
。如:
foo="bar"
Run Code Online (Sandbox Code Playgroud)
非常真实。
好吧,碰巧上面的简单赋值实际上是……实际上……声明变量的隐式方式。
(碰巧上面是改变named变量值的几种方法之一;实际上,这正是最直接、简洁、明显、直接的方法......但它不是唯一的...... . 稍后我会回到这个..foo
)。
但是,如果完全有可能声明一个“将标记变量值的名称”(为了简洁起见,以下简称“变量”)而不使用declare
,你为什么要使用这个浮夸的“声明” “ 命令 ?
答案在于,上述隐式声明变量 ( foo="bar"
) 的方式,它.. 隐式.. 使 Bash 认为该变量属于 shell 的典型使用场景中最常用的类型。
这种类型是字符串类型,即没有特定含义的字符序列。因此,当您使用隐式声明时,您会得到一个字符串。
但是你作为程序员,有时需要而考虑的一个变量,例如,数字..上,您需要做的算术运算..和使用隐含的声明一样foo=5+6
不会让Bash的分配值11 foo
,你可能会预计。它宁愿分配给foo
三个字符的序列5
+
6
。
所以..你需要一种方法来告诉 Bash 你想foo
被视为一个数字,而不是一个字符串..这就是显式declare
有用的地方。
说啊:
declare -i foo=5+6 # <<- note the '-i' option: it means 'integer'
Run Code Online (Sandbox Code Playgroud)
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
。像这样:
declare -n baz="foo"
Run Code Online (Sandbox Code Playgroud)
这告诉 Bash,从那时起,每次您将使用或更改 name 变量的值时baz
,它实际上都将使用或更改 name 变量的值foo
。
这意味着,从那时起,您可以baz=10+3
使用 make 来foo
获取 13 的值。当然,假设foo
之前declare -i
像我们一分钟前所做的那样将其声明为整数 ( ),否则它将获取 4 的序列字符1
0
+
3
。
另外:如果您foo
直接更改的值,如在 中foo=15
,您也会通过说 看到 15 echo “${baz}”
。这是因为baz
声明为 name-ref of 的变量foo
始终反映foo
的值。
上面的declare -n
命令被称为“名称引用”,因为它使变量baz
引用另一个变量的名称。事实上,我们已经声明baz
了具有值“foo”,因为这个-n
选项,它被 Bash 处理为另一个变量的名称。
现在,你到底为什么要这样做?
嗯.. 值得一提的是,这是一个满足相当高级需求的功能。
事实上如此先进,以至于当程序员面临真正需要名称引用的问题时,也很可能应该使用适当的编程语言而不是 Bash 来解决此类问题。
其中的一个先进的需求是,例如,当你作为程序员,不能在开发过程中知道哪些变量,你必须在脚本的特定点使用,但它会完全在运行时动态闻名。考虑到任何程序员都无法在运行时进行干预,唯一的选择是在脚本中预先为这种情况做好准备 ,“名称引用”可能是唯一可行的方法。例如,作为这种高级需求的一个广为人知的用例,请考虑插件。“可插入”程序的程序员需要事先为未来(也可能是第三方)插件做出通用规定。因此,程序员将需要使用 Bash 中的名称引用等工具。
另一个高级需求是当您必须处理RAM 中的大量数据,并且还需要将这些数据传递到脚本的函数中,这些函数也必须在此过程中修改这些数据。在这种情况下,你当然可以复制从一个功能到另一个数据(如bash所做的,当你这样做dest_var="${src_var}"
,或当你调用函数像myfunc "${src_var}"
),但作为数据量巨大,将作出巨大的RAM垃圾和一个非常低效运行。因此,如果出现这种情况,解决方案不是使用数据副本,而是使用引用到那个数据。在 Bash 中,名称引用。这个用例确实是任何现代编程语言的常态,但在 Bash 方面却非常特殊,因为 Bash 主要设计用于主要处理文件和外部命令的简短脚本,因此 Bash 脚本很少需要通过大量函数之间的数据量。当一个脚本的函数确实需要共享一些数据(访问它并修改它)时,这通常是通过使用一个全局变量来实现的,这在 Bash 脚本中是很常见的,因为它在适当的编程语言中非常不推荐使用。
然后,Bash 中的 name-refs 可能有一个值得注意的用例,并且(可能具有讽刺意味)它与您使用其他类型的变量时相关联:
declare -a
)declare -A
) 的变量。这些是一种可以通过使用名称引用而不是通过正常复制更容易(以及更有效地)传递函数的变量,即使它们不携带大量数据。
如果所有这些例子听起来很奇怪,仍然难以理解,那只是因为 name-refs 确实是一个高级主题,并且对于 Bash 的典型使用场景很少需要。
我可以告诉你一些我发现在 Bash 中使用 name-refs 的场合,但到目前为止,它们主要用于相当“深奥”和复杂的需求,而且恐怕如果我描述它们,我只会在你学习的这个阶段让你的事情复杂化。只是提到最不复杂的(可能并不深奥):从函数返回值。Bash 并不真正支持此功能,因此我通过使用 name-refs 获得了相同的功能。顺便说一句,这正是您的示例代码所做的。
除此之外,还有一个小小的个人建议,它实际上更适合评论,但我无法将其浓缩到足以适应 StackExchange 评论的限制。
我认为目前您最应该做的就是使用我展示的简单示例以及您提供的示例代码来试验 name-refs,暂时忽略“为什么”部分,只关注“它是如何工作的”部分。通过进行一些试验,“如何”部分可能会更好地融入您的脑海中,这样“为什么”部分就会在您遇到真正的实际问题时(或是否)在适当的时候变得清晰—— ref 真的会派上用场。
通常,declare
在bash
shell 中设置(或删除或显示)变量的属性。属性是一种注释,表示“这是一个名称引用”,或者“这是一个关联数组”,或者“这个变量应该始终被评估为一个整数”,或者“这个变量是只读的,不能重新设置”,或“此变量已导出(环境变量)”等。
所述typeset
内置为同义词declare
中bash
,如typeset
在其他外壳中使用(ksh
,它起源,和zsh
用于设定变量属性,例如)。
更仔细地查看问题中的名称参考示例:
您显示的 shell 函数,添加了一些使用它的代码:
#!/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
}
read_and_verify foo
printf 'The variable "foo" has the value "%s"\n' "$foo"
Run Code Online (Sandbox Code Playgroud)
运行这个:
$ bash script.sh 请输入 'foo' 的值:hello请重复该值以验证:hello? 价值观无与伦比。请再试一次。 变量“foo”的值为“”
这表明foo
当用户输入两个不同的字符串时,变量没有被设置为任何内容。
$ bash script.sh Please enter value for 'foo': hello 请重复输入值验证:hello 变量“foo”的值为“hello”
这表明该变量foo
被设置为用户在两次输入相同字符串时输入的字符串。
在脚本的主要部分$foo
获取值的方法hello
是通过 shell 函数中的以下几行:
declare -n ref="$1"
ref=$tmp1
Run Code Online (Sandbox Code Playgroud)
其中$tmp1
是hello
用户输入的字符串,$1
是foo
从脚本的主要部分在函数的命令行中传入的字符串。
请注意,该ref
变量被声明declare -n
为名称引用变量,并且该值foo
作为该声明中的值给出。这意味着从那时起,直到变量超出范围,对变量的任何使用ref
都将与 using 相同foo
。变量ref
是一个名称参考变量引用foo
在此点。
这会导致将值分配给ref
,就像在声明之后的行中所做的那样,会将值分配给foo
。
hello
然后该值$foo
在脚本的主要部分中可用。