如果使用声明,Bash 变量不会在数组内扩展

如果使用声明,Bash 变量不会在数组内扩展

最近,我决定阅读更多有关 bash 内置函数declarelocal和 的内容readonly,这使我从:

local variable_name
variable_name='value'
readonly variable_name

到:

variable_name='value'
declare -r variable_name

这一更改减少了编写的行数,并允许我设置一些属性,例如告诉 bash 变量的值是整数,这很好。然而,在创建一个用作 cURL 别名的函数时,我注意到如果使用 ,数组内的变量永远不会扩展,但使用和 则declare可以很好地扩展。localreadonly

这是一个例子:

#!/usr/bin/env bash

set -o errexit -o errtrace -o pipefail -o nounset
IFS=$'\n\t'

curl() {

  curl_version="$(command curl --version | awk 'NR==1 {print $2}')"
  declare -r curl_version

  curl_args=(
    --user-agent "curl/${curl_version}"
    --silent
    --fail
  )

  command curl "${curl_args[@]}" \
    "${@}"

}

curl --url 'https://httpbin.org/get'

因为变量无论出于何种原因都不会扩展,--user-agent数组的部分会使脚本退出并出现错误,因为据 bash 所知,这是一个未绑定的变量,并且由于set -o nounset.

几天来我一直在努力让它发挥作用,所以我想是时候认输并寻求帮助了。谁能指出正确的方向以了解我做错了什么?

编辑:

忘了提及,但是如果我在同一行中声明变量,例如declare -r variable_name.问题是,如果我这样做,我就会击中ShellCheck 的 SC2155,这就是为什么我试图在设置值后声明。

答案1

和:

curl_version="$(command curl --version | awk 'NR==1 {print $2}')"
declare -r curl_version

在函数中,您将$curl_version全局变量设置为某个值,然后创建一个最初未设置的单独的本地只读变量。

看起来你想要:

# instantiate a new local variable (but in bash it inherits the "export"
# attribute if any of the variable with same name in the parent scope)
local curl_version

# unset to remove that export attribute if any. Though you could
# also change the above to local +x curl_version
unset -v curl_version

# give a value:
curl_version="$(command curl --version | awk 'NR==1 {print $2}')"

# make that local variable read only
local -r curl_version

(这里使用local而不是为了declare更清楚地表明您想要将变量设置为局部变量)。

或者同时执行所有操作:

local +x -r curl_version="$(command curl --version | awk '{print $2; exit}')"

(尽管如 shellcheck 所指出的,您随后会丢失管道的退出状态²)。

无论如何,我不会像在 C中使用 / 那样在 shell 中使用readonly/ ,尤其是在. Shell(ksh93 除外)没有像 C 中那样的静态作用域。并且在(与实例相反)中,如果函数在全局作用域中设置为只读,则无法创建函数的本地变量。typeset -rconstbashbashzsh

例如:

count() {
  local n
  for (( n = 0; n < $1; n++ )) { echo "$n"; }
}

readonly n=5
count "$n"

可以在 zsh 中工作,但不能在 bash 中工作。如果您只使用local -rand never可能没问题readonly


无论如何,typeset//在declarelocal都是相同的bash,唯一的区别是,如果您尝试local在函数外部使用,它会报告错误。typeset -r和之间的区别(与和readonly之间相同)是后者如果在函数内调用则不会实例化新变量。typeset -xexport

² 看看该版本exit中如何在第一行之后停止处理输入,可以用 SIGPIPE 杀死(实际上不太可能,因为会一次发送其输出并且它会适合管道)并且因为,管道最终可能会失败并显示 141 退出状态,但只要它可以为变量赋值,它本身仍然会成功。awkawkcurlcurlpipefaillocal

答案2

该函数的第一行创建一个全球的多变的。

  curl_version="$(command curl --version | awk 'NR==1 {print $2}')"

该函数的第二行创建一个只读、空、本地多变的

  declare -r curl_version

该局部变量会覆盖全局变量的值。

请注意此摘录help declare

当在函数中使用时,“declare”使名称成为本地名称,就像使用“local”命令一样。 ‘-g’选项抑制这种行为。

我推荐这个:

curl() {

  local -r curl_version="$(command curl --version | awk 'NR==1 {print $2}')"

  local curl_args=(
    --user-agent "curl/${curl_version}"
    --silent
    --fail
  )

  command curl "${curl_args[@]}" \
    "${@}"
}

要检查变量,请declare -p curl_version curl_args在命令调用之前添加到函数中。

相关内容