1

i've been looking around for this all over, but either i don't know how to ask the question correctly, or it's not a common enough issue...

In PHP this is (almost) trivial:

function getSite(string $prefix = 'prod') {
  $DEV_SITE='dev.site.com';
  $PROD_SITE='www.site.com';

$site = strtoupper("{$prefix}_site");

return ${$site}; }

getSite(); // www.site.com getSite('dev'); // dev.site.com

i'll try to build up to it incrementally.

i have a set of variables with _SITE suffix.

DEV_SITE=dev.site.com
PROD_SITE=www.site.com

i want to call a script with JUST the prefix as an argument and default to PROD.

#!/bin/bash

PREFIX=${1:-PROD}

echo $PREFIX

$ script.sh dev dev $ script.sh PROD

i want to append "_SITE" to $PREFIX to get one of my variable names:

VAR_NAME="${1:-PROD}_SITE"
echo $VAR_NAME
---

$ script.sh dev dev_SITE $script.sh PROD_SITE

Awww, dev_SITE isn't valid, but i don't want the caller to need to know the difference.

So let's make the argument "case-insensitive" so dev will convert to match a variable name:

SITE="${1:-PROD}_SITE"
# This does NOT work:
SITE="${1:-PROD^^}_SITE"
echo ${SITE^^}
---

$ script.sh dev DEV_SITE $script.sh PROD_SITE

Great. But what's the simplest way to get the value of that name?

Everything i've tried with ${SITE^^} gives me "bad substitution" errors.

echo ${$SITE^^}_PROD
echo ${${!SITE^^}_PROD}
echo ${"${SITE^^}_PROD"}
echo ${"${!SITE^^}_PROD"}

and so on...

If i assign and reassign, it'll work:

SITE="${1:-PROD}_SITE"
SITE=${!SITE^^}
echo $SITE
---

$ script.sh dev dev.site.com $ script.sh prod.site.com

i believe it has something to do with all substitutions being done in a single step, but i've also tried subshells and piping the name into other commands, but can't get my head around it.

  • 1
    related: https://unix.stackexchange.com/questions/41406/use-a-variable-reference-inside-another-variable, https://unix.stackexchange.com/questions/255854/how-to-do-indirect-variable-evaluation and https://unix.stackexchange.com/questions/413449/does-bash-provide-support-for-using-pointers – ilkkachu Dec 23 '20 at 22:54

3 Answers3

5

Rather than doing indirect expansion, you can achieve the same result using a nameref.

From the current bash manpage:

A variable can be assigned the nameref attribute using the -n option to the declare or local builtin commands (see the descriptions of declare and local below) to create a nameref, or a reference to another variable. This allows variables to be manipulated indirectly. Whenever the nameref variable is referenced, assigned to, unset, or has its attributes modified (other than using or changing the nameref attribute itself), the operation is actually performed on the variable specified by the nameref variable's value. A nameref is commonly used within shell functions to refer to a variable whose name is passed as an argument to the function. For instance, if a variable name is passed to a shell function as its first argument, running

         declare -n ref=$1

inside the function creates a nameref variable ref whose value is the variable name passed as the first argument.References and assignments to ref, and changes to its attributes, are treated as references, assignments, and attribute modifications to the variable whose name was passed as $1. If the control variable in a for loop has the nameref attribute, the list of words can be a list of shell variables, and a name reference will be established for each word in the list, in turn, when the loop is executed. Array variables cannot be given the nameref attribute. However, nameref variables can reference array variables and subscripted array variables. Namerefs can be unset using the -n option to the unset builtin. Otherwise, if unset is executed with the name of a nameref variable as an argument, the variable referenced by the nameref variable will be unset.

Here is a version of your script that uses a nameref.

#!/bin/bash

set -u

PROD_SITE='prod.example.com' DEV_SITE='dev.example.com' PREFIX=${1:-PROD} SITEVAR=${PREFIX^^}_SITE

declare -n SITE=$SITEVAR

printf "Site is '%s'.\n" "${SITE}"

fpmurphy
  • 4,636
  • 1
    i believe this is the answer i was looking for. It's clean and simple within the code examples i wrote. i like the suggestion of an array from one of the other answers, but this is great for the original question. Thanks! – cautionbug Dec 29 '20 at 08:53
4

i have a set of variables with _SITE suffix.

If you can, don't do this. Instead use an associative array (like a basic array, but the indexes are strings) if possible. This prints www.site.com:

declare -A sites=([dev]=dev.site.com [prod]=www.site.com)
site=prod
echo "${sites[$site]}"
ilkkachu
  • 138,973
  • 2
    i had completely forgotten about bash arrays. i like this suggestion, and i can expand on it for the additional conditions i'm trying to meet (case-insensitive, etc.). It isn't technically an answer to the original question, but it's still a nice and compact alternative. Thank you. – cautionbug Dec 29 '20 at 08:56
1

Bash provides, as you have shown, a dereference utility ("Give me the value of the variable whose name this is equal to"), which makes this relatively straightforward. Though checking for invalid selections is arguably beyond the scope of this, using set -u will cause the script to abort if a variable which does not exist is referenced:

$ ./625908.sh dev
Prefix is 'DEV'.
Site is 'dev.example.com'.
$ ./625908.sh fake
Prefix is 'FAKE'.
./625908.sh: line 9: !SITEVAR: unbound variable

So, looking at this script:

#!/bin/bash
set -u
PROD_SITE='prod.example.com'
DEV_SITE='dev.example.com'
PREFIX=${1:-PROD}
PREFIX=${PREFIX^^} # Squash to uppercase
printf "Prefix is '%s'.\n" "${PREFIX}"
SITEVAR="${PREFIX}_SITE"
SITE="${!SITEVAR}"
printf "Site is '%s'.\n" "${SITE}"

This can be streamlined some, though not a ton:

#!/bin/bash
set -u
PROD_SITE='prod.example.com'
DEV_SITE='dev.example.com'
PREFIX=${1:-PROD}
SITEVAR=${PREFIX^^}_SITE
SITE="${!SITEVAR}"
printf "Site is '%s'.\n" "${SITE}"
DopeGhoti
  • 76,081
  • Bash does not provide a deference utility. Bash supports "indirect expansion" when "the first character of parameter is an exclamation point (!), and parameter is not a nameref" – fpmurphy Dec 24 '20 at 01:19
  • 1
    Thanks for the suggestion, and the explanation of set -u. i'll make note of that in future scripts. However, the code suggested doesn't really reduce any steps i already had - it just changes the first assignment of SITE to a different name. – cautionbug Dec 29 '20 at 09:04