最近,我决定阅读更多有关 bash 内置函数declare
、local
和 的内容readonly
,这使我从:
local variable_name
variable_name='value'
readonly variable_name
到:
variable_name='value'
declare -r variable_name
这一更改减少了编写的行数,并允许我设置一些属性,例如告诉 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'
因为变量无论出于何种原因都不会扩展,--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 -r
const
bash
bash
zsh
例如:
count() {
local n
for (( n = 0; n < $1; n++ )) { echo "$n"; }
}
readonly n=5
count "$n"
可以在 zsh 中工作,但不能在 bash 中工作。如果您只使用local -r
and never可能没问题readonly
。
无论如何,typeset
//在declare
中local
都是相同的bash
,唯一的区别是,如果您尝试local
在函数外部使用,它会报告错误。typeset -r
和之间的区别(与和readonly
之间相同)是后者如果在函数内调用则不会实例化新变量。typeset -x
export
² 看看该版本exit
中如何在第一行之后停止处理输入,可以用 SIGPIPE 杀死(实际上不太可能,因为会一次发送其输出并且它会适合管道)并且因为,管道最终可能会失败并显示 141 退出状态,但只要它可以为变量赋值,它本身仍然会成功。awk
awk
curl
curl
pipefail
local
答案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
在命令调用之前添加到函数中。