如果我有一个脚本将变量只读设置为一些奇数,并errexit
由于其他不安全操作而设置:
#!/bin/bash
set -e
declare -r NOTIFY=$(case "$OS" in (macosx) echo macos_notify ;; (linux) echo linux_notify ;; (*) echo : ;; esac)
declare -r SAY=_say # _say is a function
declare -r VERSION=0.99
set +e
我第二次获取它的定义,因为它正在开发中:
$ . s.bash
$ . s.bash
bash: declare: NOTIFY: readonly variable
Exited
通常declare -r EXISTING_VAR
既不会停止脚本也不会删除旧的、工作的定义EXISTING_VAR
。
但是对于errexit
,分配给现有变量是可以理解的失败。简单的选项是删除-r
或使用set +e
脚本的该部分。
除了那些,如果名称已经存在,是否可以编写一个 Bash 函数来代替declare -r
但不重新分配?
我试过:
# arg #1: var name, #2: value
set_var_once () {
# test whether the variable with the
# name stored in $1 exists
if [[ -z "${!1}" ]]
then # if it doesn't, set it
declare -r $1=$2
fi
}
我也尝试过类似的事情eval "declare -r $1=$(eval $2)"
,感觉eval
这里的某个地方需要,但我不确定在哪里。
所有版本set_var_once
都会导致未设置应有的变量。
答案1
declare -r
使变量只读,但也宣称它在当前范围内,因此使其成为当前函数的本地函数。你想要的readonly
只是前者:
readonly_once() {
local __assign
for __assign do
[[ -v ${__assign%%=*} ]] || readonly "$__assign"
done
}
用作:
readonly_once VAR1=foo VAR2="$(cmd)" PATH ...
请注意,由于与 相反readonly
, thatreadonly_once
不是关键字(是的,readonly
是还一个关键字,尽管bash
隐藏了这个事实),$(cmd)
需要引用它以防止 split+glob,此时它不是一个赋值。
$(cmd)
cmd
即使该值最终不会被分配(VAR2
如果已经定义),也会被扩展(并因此运行)。
该函数仅适用于标量变量,不适用于数组或关联数组。
答案2
如果您的 shell 是 bash,则可以使用-v
test
:
[[ -v NOTIFY ]] || NOTIFY=$(case "$OS" in (macosx) echo macos_notify ;; (linux) echo linux_notify ;; (*) echo : ;; esac)
[[ -v SAY ]] || SAY=_say # _say is a function
[[ -v VERSION ]] || VERSION=0.99
例如
$ unset myvar
$ [[ -v myvar ]] && echo "already set to $myvar" || myvar=10
$ [[ -v myvar ]] && echo "already set to $myvar" || myvar=10
already set to 10
$ myvar=5
$ [[ -v myvar ]] && echo "already set to $myvar" || myvar=10
already set to 5
$ myvar=""
$ [[ -v myvar ]] && echo "already set to $myvar" || myvar=10
already set to
或者,使用${param:=value}
扩展和:
命令
: ${NOTIFY:=$(case "$OS" in (macosx) echo macos_notify ;; (linux) echo linux_notify ;; (*) echo : ;; esac)}
: ${SAY:=_say}
: ${VERSION:=0.99}
证明:
$ OS=macosx
$ echo "$NOTIFY"
$ : ${NOTIFY:=$(case "$OS" in (macosx) echo macos_notify ;; (linux) echo linux_notify ;; (*) echo : ;; esac)}
$ echo "$NOTIFY"
macos_notify
$ NOTIFY=no
$ : ${NOTIFY:=$(case "$OS" in (macosx) echo macos_notify ;; (linux) echo linux_notify ;; (*) echo : ;; esac)}
$ echo "$NOTIFY"
no
$ NOTIFY=""
$ : ${NOTIFY:=$(case "$OS" in (macosx) echo macos_notify ;; (linux) echo linux_notify ;; (*) echo : ;; esac)}
$ echo "$NOTIFY"
macos_notify
答案3
我发现以下内容很有用
# Function SetCommand
# Function to find a command and assign the absolute path of that command to
# a variable. The intent is to only invoke known good commands.
# If a command is not found, abort. Assume the script needed this command.
# Function is called as follows:
# SetCommand assignmentVariableName queryString
#
# where
# assignmentVariableName is the name of the variable to which the path
# is assigned,
# queryString is the name of the command
#
# Example: SetCommand CMD_FOO foo
#
SetCommand() {
local _assignmentVariableName
local _fullPath
local _queryString
[ $# -ne 2 ] && AbortScript "${FUNCNAME}: Invalid number of arguments."
_assignmentVariableName="$1"
[[ "" == "${_assignmentVariableName}" ]] && AbortScript "${FUNCNAME}: assignmentVariableName is blank."
shift
_queryString="$1"
[[ "" == "${_queryString}" ]] && AbortScript "${FUNCNAME}: queryString is blank."
shift
if [[ ! -z ${!_assignmentVariableName+x} ]]; then
Print2Stderr "${FUNCNAME}: ${_assignmentVariableName} already defined."
return ${constErrorExitCode}
fi
_fullPath=$(${CMD_WHICH} ${_queryString} 2>/dev/null)
[[ "" == "${_fullPath}" ]] && AbortScript "${FUNCNAME}: Could not find command, ${_queryString}."
eval readonly ${_assignmentVariableName}=${_fullPath}
return ${constSuccessExitCode}
} # End SetCommand
AbortScript 是我使用的另一个函数。它只是打印出错误消息,然后退出脚本。