Function to conditionally set a variable read-only

If I had a script which sets variables read-only to some odd values, and sets errexit because of other unsafe operations:

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

And I source it to get the definitions, the second time because it’s in development:

$ . s.bash 

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

Normally declare -r EXISTING_VAR would neither stop the script nor remove the old, working definition of EXISTING_VAR.

But with errexit, assigning to an existing variable is understandably a failure. The easy options are to remove -r or use set +e for that part of the script.

Barring those, is it possible to write a Bash function to take the place of declare -r but not re-assign if the name already exists?

I tried:

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

I also tried things along the lines of eval "declare -r $1=$(eval $2)", it feels like eval is required somewhere here but I’m not sure where.

All of the versions of set_var_once result in not setting the variable they should.

Answer

declare -r make a variable readonly but also declares it in the current scope and so makes it local to the current function. You’d want readonly instead that only does the former:

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

To be used as:

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

Note that since contrary to readonly, that readonly_once is not a keyword (yes, readonly is also a keyword even though bash keeps that fact hidden), that $(cmd) needs to be quoted to prevent split+glob, it’s not an assignment at that point.

$(cmd) will be expanded (and so cmd run) even if the value will end-up not being assigned to VAR2 if it was already defined.

That function only works for scalar variables, not arrays nor associative arrays.

Leave a Reply

Your email address will not be published. Required fields are marked *