Bash 中的“声明”是什么?

Bash 中的“声明”是什么?

阅读 ilkkachu 的回答后这个问题我了解到内置declare(带有参数)shell 的存在。-n

help declare带来:

设置变量值和属性。

声明变量并赋予它们属性。如果未给出名称,则显示所有变量的属性和值。

-n ...使 NAME 成为对其值命名的变量的引用

我要求提供一个带有示例的一般解释,declare因为我不明白man.我知道什么是变量并扩展它,但我仍然想念manon 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
}

答案1

在大多数情况下,隐式声明就足够了bash

asdf="some text"

但是,有时您希望变量的值仅是整数(因此,如果它稍后会更改,即使是自动更改,它也只能更改为整数,在某些情况下默认为零),并且可以使用:

declare -i num

或者

declare -i num=15

有时你需要数组,然后你需要declare

declare -a asdf   # indexed type

或者

declare -A asdf   # associative type

bash例如,当您使用搜索字符串“bash arraytutorial”(不带引号)浏览互联网时,您可以找到有关数组的优秀教程

linuxconfig.org/how-to-use-arrays-in-bash-script


我认为这些是声明变量时最常见的情况。


另请注意,

  • 在函数中,declare使变量成为局部变量(在函数中)
  • 没有任何名称,它列出所有变量(在活动 shell 中)

    declare
    

declare最后,您可以通过bash以下命令获得 shell 内置命令功能的简要总结

help declare

答案2

的输出help declare非常简洁。更清晰的解释可以在man bash或中找到info bash——后者是下文的来源。

首先,一些定义。关于变量和属性:

A范围是一个存储值的实体。 ... A多变的是由 a 表示的参数name。一个变量有一个价值以及零个或多个属性。使用内置命令分配属性declare...

还有关于declare 内置:

declare

declare [-aAfFgilnrtux] [-p] [name[=value] …]

声明变量并赋予它们属性。如果未给出名称,则显示变量的值。

...

-n
给每个姓名名称引用属性,使其成为对另一个变量的名称引用。该另一个变量由以下值定义姓名。所有引用、分配和属性修改姓名,除了那些使用或更改-n属性本身的操作外,均在由 引用的变量上执行姓名的值。 ...

注意名称参考变量仅在 Bash 4.3 或更高版本中可用1

另外,对于declareBash 中的变量属性的有用介绍,我会向您指出这个答案到“做什么declare namedeclare -g做什么?”(不过,主要关注变量的范围)。


基本上2declare name=[value]相当于name=[value]您可能熟悉的作业。在这两种情况下,如果缺少,name则分配空值。value

请注意,略有不同declare name, 相反,并不变量name3

$ 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

因此,该变量name可以是:

  • 宣布未设置, 后declare name;
  • 宣布无效的作为值,在name=or之后declare name=
  • 宣布,并与非空name=value或后的值declare name=value

更普遍,declare [options] name=value

  1. 创建变量name- 这是一个带有名称的参数,而它又只是可用于存储信息的内存的一部分4
  2. 为其赋值value
  3. 可选择设置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

鉴于上述情况,ilkkachu 的代码中发生的事情是:

  1. 声明了一个名为 name 的变量ref,设置了“nameref”属性,并将(第一个位置参数)的内容$1分配给它:

    declare -n ref="$1"
    

    名称引用变量的目的ref是保存另一个变量的名称,该名称通常不会提前知道,可能是因为我们希望它是动态定义的(例如,因为我们想要重用一段代码并拥有它)应用于多个变量),并提供一种方便的方式来引用(和操作)它。 (不过,不是唯一的:间接是一种替代方法;请参阅Shell 参数扩展)。

  2. 当变量的值tmp1被赋值为ref

    ref=$tmp1
    

    ref隐式声明了一个附加变量,其名称为 的值。的值tmp1也是间接地通过对 的显式赋值来分配给隐式声明的变量ref

在这样的背景下你的链接问题,调用read_and_verify

read_and_verify domain "Prompt text here..."

将声明该变量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

1参考资料:变化文件,“3. Bash 中的新功能”部分,点“w”。
这可能是相关的:例如,CentOS Linux 7.6(当前最新版本)随 Bash 4.2 一起提供

2与 shell 内置函数一样,详尽的简洁的解释是难以捉摸的,因为它们执行各种不同的、可能是异构的动作。我将只关注声明、分配和设置属性,并且我将考虑列出、确定范围和删除属性,这超出了本答案的范围。

3 Bash 4.4 中引入了这种行为。declare -p参考:变化文件,“3. Bash 中的新功能”部分,点“f”。
作为G-曼评论中指出,在 Bash 4.3 中declare name; declare -p name会产生错误。但您仍然可以使用name来检查是否存在declare -p | grep 'declare -- name'

4完整的 Bash 指南,参数在 mywiki.wooledge.org 上

答案3

我会尽力解释这一点,但如果我不遵循您提供的示例,请原谅我。我宁愿尝试引导您采用我自己的不同方法。

你说你已经理解了“变量”和“扩展它们”等概念,所以我只会浏览一些背景知识,否则需要更深入的关注。

所以我首先要说的是,最多基本的级别,该declare命令只是告诉 Bash 您需要一个变量值(即在脚本执行期间可能会更改的值)的一种方式,并且您将使用特定名称引用该值,正是您接下来指定的名称命令declare本身。

那是:

declare foo="bar"

告诉 Bash 您希望名为 的变量foo具有值bar

但是..等一下..我们可以在不使用的declare情况下做到这一点,不是吗?如:

foo="bar"

非常真实。

嗯,碰巧上面的简单赋值实际上是一个隐含的事实上..声明一个变量的方法。

也正巧上面是的几种方法改变名为 的变量的值foo;确实,这正是最直接、简洁、明显、直截了当的方式..但它不是唯一的方式.. ..我稍后会回来讨论这一点..)。

但是,如果完全有可能在不使用的情况下声明一个“将标记变量值的名称”(为了简洁起见,此后简称为“变量”)declare ,那么您为什么要使用这个浮夸的“声明” “ 命令 ?

答案在于,上述声明变量的隐式方式 ( foo="bar"),它……隐式地……使 Bash 认为该变量属于 shell 的典型使用场景中最常用的类型。

这种类型就是字符串类型,即没有特定含义的字符序列。因此,当您使用隐式声明时,您将得到一个字符串。

但是,作为程序员,您有时需要将变量视为例如数字..您需要对其进行算术运算..并使用像这样的隐式声明foo=5+6 惯于foo让 Bash按照您的预期分配值 11 。它宁愿分配给foo三个字符的序列5 + 6

所以..你需要一种方法来告诉Bash你想foo 被视为一个数字,而不是一个字符串..这就是显式的declare 有用之处。

说啊:

declare -i foo=5+6  # <<- note the '-i' option: it means 'integer'

Bash 会很乐意为你做数学计算,并分配数字值 11 到变量foo

也就是说:通过说declare -i foo你给foo变量属性是一个整数。

声明数字(准确地说是整数,因为 Bash 仍然不理解小数、浮点数等等)可能是使用 的第一个原因declare,但这不是唯一的原因。正如您已经了解的,您还可以为变量赋予一些其他属性。例如,无论怎样,您都可以让 Bash 始终将变量的值设为大写:如果您说declare -u foo,那么从那时起,当您说foo=barBash 实际上将字符串分配BAR给变量 时foo

为了将这些属性中的任何一个赋予变量,您必须使用declare命令,没有其他选择。


现在,您可以提供的另一个属性declare是臭名昭著的“name-ref”属性,即-n属性。 (现在我要恢复之前搁置的概念)。

基本上,name-ref 属性允许 Bash 程序员使用另一种方法来更改变量的值。它更准确地给出了间接方法来做到这一点。

这是如何有用:

你是declare一个具有属性的变量-n,它是非常推荐(虽然不是严格要求,但它使事情变得更简单),你也给一个值同一个declare命令的变化很大。像这样:

declare -n baz="foo"

这告诉 Bash,从那时起,每次您使用或更改名为 的变量的值时baz,它都应实际使用或更改名为 的变量的值foo

这意味着,从那时起,你可以说类似baz=10+3make fooget 的值 13 。当然假设它foo之前被声明为整数(declare -i),就像我们一分钟前所做的那样,否则它将得到四个的序列人物1 0 + 3

另外:如果你foo直接改变 的值,就像在 中一样foo=15,你也会通过说 看到 15 echo “${baz}”。这是因为baz声明为 name-ref of 的变量foo始终反映foo的值。

上面的declare -n命令被称为“名称引用”,因为它使变量baz 参考姓名的另一个变量。事实上,我们已经声明了baz 具有值“foo”,由于该-n选项,该值被 Bash 处理为另一个变量的名称。

现在,为什么在地球上你会想这样做吗?

嗯..值得一提的是,这是一个满足相当高级需求的功能。

事实上,如此先进以至于当程序员面临真正需要名称引用的问题时,也很可能应该使用适当的编程语言而不是 Bash 来解决此类问题。

例如,这些高级需求之一是,当您作为程序员在开发过程中无法知道时哪个您必须在脚本的特定点使用变量,但它将要在运行时动态地被完全了解。鉴于任何程序员都无法在运行时进行干预,唯一的选择就是做出规定 预先对于脚本中的这种情况,“name-ref”可能是唯一可行的方法。作为这种高级需求的一个广为人知的用例,可以考虑插件。 “可插件”程序的程序员需要事先为未来(可能还有第三方)插件做好通用准备。因此,程序员需要使用 Bash 中的 name-ref 之类的工具。

另一项高级需求是当您必须处理大量数据时在内存中和您还需要在脚本的函数中传递该数据必须一路修改该数据。在这种情况下你当然可以复制数据从一个函数传输到另一个函数(就像 Bash 所做的那样,dest_var="${src_var}"或者当您调用 in 之类的函数时myfunc "${src_var}"),但数据量很大,会造成 RAM 的巨大浪费对于非常低效的操作。因此,如果出现这种情况,解决方案是不使用数据的副本,而是使用参考到该数据。在 Bash 中,名称引用。这种用例确实是任何现代编程语言中的常态,但对于 Bash 来说却是相当例外的,因为 Bash 主要是为简短的简单脚本而设计的,这些脚本主要处理文件和外部命令,因此 Bash 脚本很少需要传递巨大的函数之间的数据量。当脚本的函数确实需要共享一些数据(访问并修改它)时,通常只需使用全局变量即可实现,这在 Bash 脚本中很常见非常在适当的编程语言中已弃用。

然后,Bash 中的 name-refs 可能有一个值得注意的用例,并且(也许具有讽刺意味的是)它与您使用其他类型的变量时相关联:

  1. 声明为“索引数组”的变量 ( declare -a)
  2. 声明为“关联数组”的变量 ( declare -A)。

这些是一类变量,可能是更容易(以及更有效地)通过使用名称引用而不是通过正常复制来传递函数,即使它们不携带大量数据。

如果所有这些例子听起来很奇怪,并且仍然难以理解,那只是因为 name-refs 确实是一个高级主题,并且对于 Bash 的典型使用场景来说很少需要。

我可以告诉你一些我在 Bash 中发现名称引用的用途的情况,但到目前为止,它们大多是为了相当“深奥”和复杂的需求,而且我担心如果我描述它们,我只会在你学习的这个阶段,事情会变得复杂。只提一下最不复杂的(可能并不深奥):从函数返回值。 Bash 并不真正支持此功能,因此我通过使用 name-refs 获得了相同的功能。顺便说一句,这正是您的示例代码所做的。


除此之外,还有一个小小的个人建议,实际上更适合发表评论,但我无法将其浓缩到足以适应 StackExchange 评论的限制。

我觉得最多您现在应该做的就是使用我展示的简单示例以及您提供的示例代码来尝试 name-refs,暂时忽略“到底为什么”部分,只关注“它是如何工作的” “ 部分。通过一点实验,“如何”部分可能会更好地融入你的脑海,这样,当(或者如果)你遇到一个真正的实际问题时,“为什么”部分就会在适当的时候变得清晰起来,这个问题的名字是—— ref 确实会派上用场。

答案4

Declare 是 Bash 中声明变量的几种语法之一:

x=7
declare x=7
declare x

function something {
  local x
  local x=7
}

这些声明变量的不同方式是 shell 逐渐演变的结果。

-n是一种声明有时称为“变量变量”的方式——它是一个与另一个变量绑定的变量。您可以测试以下脚本在线的:

#!/bin/bash
set -o errexit -o nounset -o pipefail

declare thevariable
declare -n thevariablevariable=thevariable

thevariable='the value'

printf 'thevariable: %s\n' "$thevariable"
printf 'thevariablevariable: %s\n' "$thevariablevariable"

thevariablevariable='the other value'

printf 'thevariable: %s\n' "$thevariable"
printf 'thevariablevariable: %s\n' "$thevariablevariable"

运行它时您看到的thevariablevariable不是指向 的指针thevariable,而是 的别名thevariable

thevariable: the value
thevariablevariable: the value
thevariable: the other value
thevariablevariable: the other value

现在,这可能看起来像是一个狡猾的错误功能,但实际上,它是一个不寻常的语言功能。然而,shell 编程有时需要奇怪的功能。

我们在 shell 中声明变量的方法如此之多,这似乎很奇怪,因为 shell 理论上是一种非常简单且 lofi 语言;但这与我们编程方式的逐渐改变有关。一个例子是函数局部变量定义。

设置值的最简单方法显然是x=7;但是当我们有一个带有函数的脚本时,以下是什么意思?

#!/bin/bash
set -o errexit -o nounset -o pipefail

x=7

function something {
  x=6
}

something

printf 'What is x? It is: %s\n' "$x"

好吧,一种合理的解释是,它x仍然应该位于7脚本的末尾;但实际上它是 6,因为曾经有一段时间,函数中的声明是局部的并不是程序员的普遍期望。经验告诉我们,局部函数是一种很好的实践;但是我们不能简单地改变shell中声明的含义,因为这会破坏所有旧的脚本。因此,我们引入了一种新的声明语法。

相关内容