4

AFAICT, neither GNU Bash, nor any other relevant package (e.g. GNU coreutils) that is commonly available in GNU/Linux distros, has a ready-made way to define an environment variable such that the attempt will fail, and indicate failure, if the environment variable already exists. Essentially, a "no-clobber" option for variable assignment, with a non-zero exit status on failure.

Shell parameter expansion (aka "parameter substitution") approaches avoid clobbering, but do not report failure:

unset MY_VARIABLE
: ${MY_VARIABLE:='my_value'}
: ${MY_VARIABLE:='this_will_fail_but_will_nevertheless_have_exit_code_0'}
echo $?
0
echo $MY_VARIABLE
my_value

The best workaround I have come up with is to create a function ("safedef") for that purpose:

die() {
    echo -e >&2 "$@"
    exit 1
}

safedef() {
    if [ -v $1 ]; then
        die 'Name conflict. Variable already defined. Will not overwrite it.'
    else
        # Create and assign environment variable
        export $1="${*:2}"
    fi
}

safedef MY_VARIABLE 'my_value'
safedef MY_OTHER_VARIABLE 'my_other_value'
# Etc, etc...

Questions:

  1. Have I overlooked a ready-made way to achieve this, provided by a free software package that is available in some or all mainstream GNU/Linux distros?
  2. Have I overlooked some kind of bug in safedef that means it could well unexpectedly fail to work as intended? (If so, what is the bug, and what might be a better approach?)
8
  • why not mark things-not-to-be-changed as readonly ?
    – thrig
    May 7, 2018 at 15:24
  • Do you only need to consider scalar variables that have been exported to the environment (or where already in the environment when the shell was invoked)? Or any variable scalar or not (array, hash...), exported or not? May 7, 2018 at 15:26
  • @thrig, the problem with marking existing variables as read-only is twofold. 1. the problem of detecting their existence at the relevant moment (which seems more fraught with potential race conditions and other complications than my safedef approach above); and 2. because if the existing variable was defined by some other process, and that process needs to be able to overwrite it, then my marking it as read-only might cause that other process to fail (and indeed to do so in a subtle and headache-inducing manner).
    – user6860
    May 7, 2018 at 16:21
  • @StéphaneChazelas, "Do you only need to consider scalar variables that have been exported to the environment (or where already in the environment when the shell was invoked)?" At the moment, yes, that's my interest, and I would be happy to mark as correct an answer providing this, or confirming that safedef achieves it and can't readily be bettered. However, ... "Or any variable scalar or not (array, hash...), exported or not?" ... it would certainly be nice to have a general solution :)
    – user6860
    May 7, 2018 at 16:55
  • 2
    @sampablokuper "because if the existing variable was defined by some other process, and that process needs to be able to overwrite it." Please note that each process has its own copy of the environment, changing the value of a variable does not change the value in the parent process. Also, the readonly attribute on an environment variable is not inherited by the child process, the inherited environment variables start as read-write. May 7, 2018 at 19:17

1 Answer 1

2

In bash I found the following:

unset MY_VARIABLE
: ${MY_VARIABLE='my_value'}
MY_VARIABLE=${MY_VARIABLE:+$( false )}${MY_VARIABLE:-'this_will_fail_with_exit_1'}
echo $?
echo $MY_VARIABLE

This prints:

1
my_value
1
  • On the face of it, this (i.e. the third line in your code block) seems to be a pretty elegant solution, making smart use of the parameter expansion options that GNU Bash provides. Thanks :) A nitpick is that it would be nice if it were DRY, i.e. if MY_VARIABLE only had to be written once for each invocation of that solution. But this is a nice starting point and a great answer, thanks again :)
    – user6860
    May 9, 2018 at 2:14

You must log in to answer this question.