有条件地将变量设置为只读的函数

有条件地将变量设置为只读的函数

如果我有一个脚本将变量只读​​设置为一些奇数,并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 是我使用的另一个函数。它只是打印出错误消息,然后退出脚本。

相关内容