Renaming environment variables by changing variable name prefix

Using variable name prefix matching and variable indirection in bash:

proj_env_repo_db_username=username
proj_env_repo_db_host=host
proj_env_repo_db_port=port

for variable in "${!proj_env_repo@}"; do
    export "TF_ENV${variable#proj_env_repo}"="${!variable}"
done

The loop uses "${!proj_env_repo@}" to generate a list of variable names that share the name prefix proj_env_repo. In each iteration $variable will be the name of one of these variables.

Inside the loop, export is used to create a new environment variable by stripping off the proj_env_repo prefix and replacing it with the string TF_ENV. The value for the new environment variable is had using the variable indirection ${!variable}, i.e. the value of the variable whose name is stored in $variable.

To additionally unset the original variable, use unset "$variable" after export, before the end of the loop.

Test running this with tracing turned on:

$ bash -x script.sh
+ proj_env_repo_db_username=username
+ proj_env_repo_db_host=host
+ proj_env_repo_db_port=port
+ for variable in "${!proj_env_repo@}"
+ export TF_ENV_db_host=host
+ TF_ENV_db_host=host
+ for variable in "${!proj_env_repo@}"
+ export TF_ENV_db_port=port
+ TF_ENV_db_port=port
+ for variable in "${!proj_env_repo@}"
+ export TF_ENV_db_username=username
+ TF_ENV_db_username=username

As a function taking the old name prefix as its 1st argument and the new prefix as its 2nd argument:

rename_var () {
    # Make sure arguments are valid as variable name prefixes
    if ! [[ $1 =~ ^[a-zA-Z_][a-zA-Z_0-9]*$ ]] ||
       ! [[ $2 =~ ^[a-zA-Z_][a-zA-Z_0-9]*$ ]]
    then
        echo 'bad variable name prefix' >&2
        return 1
    fi

    eval 'for variable in "${!'"$1"'@}"; do
              export "'"$2"'${variable#'"$1"'}"="${!variable}"
          done'
}

We resort to using eval over the loop here since bash does not support the syntax ${!$1@}. The function constructs the appropriate shell code (as a string) for renaming the variables according to the values of $1 and $2 (the 1st and 2nd argument given to the function), and then uses eval to execute this shell code.

You would use this function as

rename_var project_env_repo TF_ENV

... or, using variables,

rename_var "$old_variable_prefix" "$new_variable_prefix"

Note: When doing things like these (using eval on user input), you must test that the code that you evaluate is valid and that it is what you expect it to be. In this case this mean validating $1 and $2 as valid variable name prefixes. Otherwise at least quotes and } will cause syntax errors in the eval, and there is a possibility of command injection.


Note: This is the first time (I think) that I've ever had to use eval. I would never put myself in the position of having to use the above code though, but I don't know the background story to the question, obviously, so that's not real criticism of the question (which is an interesting one in itself).

Related (on the Software Engineering site):

  • Why are eval-like features considered evil, in contrast to other possibly harmful features?

while IFS='=' read -r name value; do
    new_name="TF_ENV${name#proj_env_repo}"
    export "$new_name=$value"
    unset "$name"
done < <( printenv | grep proj_env_repo )

The secret ingredients here:

  • Shell Parameter Expansion
  • IFS='=' read -r name value -- uses the = sign to split the incoming line into the component parts.
  • using a Process Substitution so the while loop runs in the current shell.