Função para definir condicionalmente uma variável somente leitura

3
 

Se eu tivesse um script que define variáveis somente leitura para alguns valores ímpares, e define errexit devido a outras operações inseguras:

#!/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 

E eu o forneço para obter as definições, a segunda vez porque está em desenvolvimento:

$ . s.bash 

$ . s.bash 
bash: declare: NOTIFY: readonly variable
Exited

Normalmente, declare -r EXISTING_VAR não interromperia o script nem removeria a antiga definição de trabalho de EXISTING_VAR .

Mas com errexit , atribuir a uma variável existente é compreensivelmente uma falha. As opções fáceis são remover -r ou usar set +e para essa parte do script.

Bloqueando-os, é possível escrever uma função Bash para substituir o declare -r , mas não reatribuir se o nome já existe ?

Eu tentei:

# 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
}

Eu também tentei as coisas ao longo das linhas de eval "declare -r $1=$(eval $2)" , parece que eval é necessário em algum lugar aqui, mas não tenho certeza de onde.

Todas as versões de set_var_once resultam em não definir a variável que devem.

    
por cat 29.10.2018 / 20:52

3 respostas

2

declare -r faz uma variável readonly mas também declara no escopo atual e assim torna-a local para a função atual. Você quer o readonly em vez disso, apenas o primeiro:

readonly_once() {
  local __assign
  for __assign do
    [[ -v ${__assign%%=*} ]] || readonly "$__assign"
  done
}

Para ser usado como:

readonly_once VAR1=foo VAR2="$(cmd)" PATH ...

Observe que, desde que contrário a readonly , esse readonly_once não é uma palavra-chave (sim, readonly é também uma palavra-chave, embora bash mantenha esse fato oculto), $(cmd) precisa ser citado para evitar split + glob, não é uma atribuição nesse ponto.

$(cmd) será expandido (e assim cmd run), mesmo que o valor acabe não sendo atribuído a VAR2 se já tiver sido definido.

Essa função só funciona para variáveis escalares, não matrizes nem matrizes associativas.

    
por 29.10.2018 / 21:47
1

Se o seu shell é bash, você pode usar o -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

Por exemplo

$ 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 

Ou use a expansão ${param:=value} e o comando :

: ${NOTIFY:=$(case "$OS" in (macosx) echo macos_notify ;; (linux) echo linux_notify ;; (*) echo : ;; esac)}
: ${SAY:=_say}
: ${VERSION:=0.99}

demonstrando:

$ 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
    
por 29.10.2018 / 22:28
1

Eu encontrei o seguinte para ser útil

# 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

O AbortScript é outra função que eu uso. Apenas imprime a mensagem de erro e sai do script.

    
por 29.10.2018 / 21:08