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

Jam*_*ond 7 bash array shell-script curl readonly

最近,我决定读多一点关于bash的内置插件declarelocalreadonly,这使我从切换:

local variable_name
variable_name='value'
readonly variable_name
Run Code Online (Sandbox Code Playgroud)

到:

variable_name='value'
declare -r variable_name
Run Code Online (Sandbox Code Playgroud)

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

下面是一个例子:

#!/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'
Run Code Online (Sandbox Code Playgroud)

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

几天来我一直试图让它发挥作用,所以我想是时候扔掉毛巾寻求帮助了。任何人都可以指出我正确的方向以了解我做错了什么吗?

编辑:

忘了提及,但如果我在同一行中声明该变量确实会扩展,例如declare -r variable_name. 问题是,如果我这样做,我会从 ShellCheck 中点击SC2155,因此我尝试在设置值后进行声明。

Sté*_*las 12

和:

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

在函数中,您将$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
Run Code Online (Sandbox Code Playgroud)

(这里使用local而不是declare更清楚地说明您想要将变量设为本地¹)。

或者同时使用:

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

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

无论如何,我不会像在 C 中使用的那样在 shell 中使用readonly/ typeset -rconst尤其是在bash. Shell(除了 ksh93)没有像 C 中那样的静态作用域。在bash(与zsh例如相反)中,如果函数在全局作用域中被设为只读,则不能创建函数的局部变量。

例如:

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

readonly n=5
count "$n"
Run Code Online (Sandbox Code Playgroud)

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


¹在任何情况下typeset/ declare/local都是一样的bash,唯一的区别是,如果您尝试使用local功能之外,它报告错误。typeset -rand之间的区别readonly(与typeset -xand之间相同export)在于后者如果在函数内调用则不会实例化新变量。

²与了解如何exitawk该版本awk停止处理第一线后的输入,curl可以用一个SIGPIPE(在实践中是非常不太可能,因为被杀死curl将派遣其输出一气呵成,它会适合在管道)和因pipefail,管道最终可能会以 141 退出状态失败,但local只要它可以为变量赋值,它本身仍然会成功。


gle*_*man 7

函数的第一行创建了一个全局变量。

  curl_version="$(command curl --version | awk 'NR==1 {print $2}')"
Run Code Online (Sandbox Code Playgroud)

函数的第二行创建一个只读的、空的、LOCAL变量

  declare -r curl_version
Run Code Online (Sandbox Code Playgroud)

此局部变量覆盖全局变量的值。

请注意以下摘录help declare

当在函数中使用时,`declare' 使 NAME 成为本地的,就像 `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[@]}" \
    "${@}"
}
Run Code Online (Sandbox Code Playgroud)

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

  • @JamesPond,它确实像 `local` 一样工作,这就是这里的重点。 (3认同)