Bash

如果使用聲明,bash 變數不會在數組內擴展

  • June 29, 2021

最近我決定閱讀更多關於 bash 內置函式declarelocalreadonly的內容,這導致我從:

local variable_name
variable_name='value'
readonly variable_name

至:

variable_name='value'
declare -r variable_name

此更改減少了編寫的行數並允許我設置一些屬性,例如告訴 bash 變數的值是整數,這很好。但是,在創建將用作 cURL 別名的函式時,我注意到如果我使用數組中的變數永遠不會擴展,而使用anddeclare擴展就好了。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,因此我試圖在設置值後聲明。

和:

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如果你只使用並且從不使用它可能沒問題readonly


¹ 在任何情況下typeset//在declarelocal都是一樣的bash,唯一的區別是如果你嘗試在local函式之外使用,它會報告錯誤。and之間的區別(與typeset -randreadonly相同)是後者在函式內呼叫時不會實例化新變數。typeset -x``export

² 看看exitawk那個版本中如何awk在第一行之後停止處理輸入,curl可能會被 SIGPIPE 殺死(實際上不太可能curl一次性發送它的輸出並且它會適合管道)並且因為pipefail,管道最終可能以 141 退出狀態失敗,但local只要它可以為變數賦值,它本身仍然會成功。

引用自:https://unix.stackexchange.com/questions/656179