4

I have a few environment variables. There are multiple such values I want to convert dynamically.

Env variable: I'm getting this env variable using printenv | grep proj_env_repo ( doing like printenv | grep $value).

so the value is proj_env_repo

and my results are

proj_env_repo_db_username=username

I want to convert it to TF_ENV_db_username=username

Can someone help me with it? I'm looking for a function in bash script.

  • Do you have many variables with the same name prefix? – Kusalananda Oct 16 at 20:40
  • There will be two variables with the same prefix for each proj – Swat Oct 16 at 20:43
8

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):

  • what does #proj_env_repo do? what if i need to pass as a variable like $value? would it take that? – Swat Oct 16 at 21:20
  • @swat ${something#prefix} removes the string prefix from the beginning of the value of $something. So ${variable#proj_env_repo} removes the string proj_env_repo from beginning of the value $variable (which is the name of one of those variables whose name starts with proj_env_repo). For you second follow up question, see the function towards the second half of my answer. – Kusalananda Oct 16 at 21:24
3
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:

  • why its not working for me, printenv | grep $new_value | while IFS='=' read -r name value; do new_name="TF_ENV${name#$new_value}"; export "$new_name=$value"; done where $new_value=proj_env_repo – Swat Oct 16 at 21:33
  • 1
    @Kusalananda, d'oh! I'll fix. – glenn jackman Oct 17 at 0:50
  • @Swat, please try my edited answer. – glenn jackman Oct 17 at 0:53
  • Will there be issues with variables holding newlines? – Kusalananda Oct 17 at 7:02
1
for v in `printenv | grep proj_env_repo`
do
    eval `echo "$v=\$$v" | sed -e 's/proj_env_repo/TF_ENV/'`
    unset $v
done

The echo prints:

proj_env_repo_db_username=$proj_env_repo_db_username

The sed converts the first instance of proj_env_repo with TF_ENV.

TF_ENV_db_username=$proj_env_repo_db_username

The eval evaluates that statement.

The unset makes sure the old variable gets deleted.

  • Will there be issues with variables holding newlines or other whitespace? E.g. proj_env_repo_db_string=$'hello\nworld' – Kusalananda Oct 17 at 7:03
  • Note that $v is both the name and the value, so you can't unset it, nor can you use it as an identifier. – Kusalananda Oct 17 at 7:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.