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?)
  • why not mark things-not-to-be-changed as `readonly` ? – thrig May 07 '18 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? – Stéphane Chazelas May 07 '18 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). –  May 07 '18 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 :) –  May 07 '18 at 16:55
  • Does the solution need to be POSIX-compliant or it is ok to use some non-standard extensions? – nxnev May 07 '18 at 17:37
  • @nxnev, I tagged this question with `bash` and `gnu` rather than with `POSIX` ;) POSIX-compliance would be nice, but is not crucial, as long as the solution would work in a GNU Bash script on most mainstream GNU/Linux distros. If you have a solution in mind, the best thing would probably be to post it and to note the ways in which it is not POSIX-compliant. Thanks :) –  May 07 '18 at 17:53
  • 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. – Johan Myréen May 07 '18 at 19:17
  • @JohanMyréen, kicking myself: you are of course quite right about other processes having their own environments. I wasn't aware, though, that child processes inherit read-only variables as read-write: thanks for that piece of info. So, would your recommended approach be to mark all the existing environment variables as read-only at the start of the script, and if so, would you (or nxnev) like to write that up as an answer? –  May 07 '18 at 21:40

1 Answers1

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
Marco
  • 421
  • 2
  • 6
  • 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 :) –  May 09 '18 at 02:14