0

I'm trying to use bash's variable name expansion, but I can't seem to get this to work. ${!${prefix}*} in particular is what is failing. Below is a reproducible example.

#!/bin/bash

MountVolumes_b_mkfs_options='foo bar baz'
MountVolumes_b_path=/foo/bar/baz
MountVolumes_b_mnt_options='foo bar baz'
MountVolumes_b_fs=xfs
MountVolumes_c_path=/foo/bar/baz
MountVolumes_c_fs=xfs
MountVolumes_c_mkfs_fs_options=-'t really -foo /ugly/options'
MountVolumes_c_mkfs_options='-t really -foo /ugly/options'

prefixes=($(echo "${!MountVolumes*}" | grep 'MountVolumes_[b-z]' -o | uniq))

for prefix in ${prefixes[@]}; do
  echo "prefix: ${prefix}"

  ##I need this to expand to: 
  ##MountVolumes_b_mkfs_options MountVolumes_b_path MountVolumes_b_mnt_options MountVolumes_b_fs
  echo "${!${prefix}*}" 
done

echo "${!MountVolumes_b*}" ##Works

How do I do this?

1
  • Search the documentation for nameref. I don't have time to give a complete answer, but I think that's what you want. Commented May 21, 2020 at 18:34

3 Answers 3

2

This can achieve what's needed :

#!/bin/bash

MountVolumes_b_mkfs_options='foo bar baz'
MountVolumes_b_path=/foo/bar/baz
MountVolumes_b_mnt_options='foo bar baz'
MountVolumes_b_fs=xfs
MountVolumes_c_path=/foo/bar/baz
MountVolumes_c_fs=xfs
MountVolumes_c_mkfs_fs_options=-'t really -foo /ugly/options'
MountVolumes_c_mkfs_options='-t really -foo /ugly/options'

prefixes=($(echo "${!MountVolumes*}" | grep 'MountVolumes_[b-z]' -o | uniq))

for prefix in ${prefixes[@]}; do
  echo "prefix: ${prefix}"

  ##I need this to expand to: 
  ##MountVolumes_b_mkfs_options MountVolumes_b_path MountVolumes_b_mnt_options MountVolumes_b_fs
  declare -a "vars=(\${!${prefix}*})"
  echo "${vars[@]}"
done

echo "${!MountVolumes_b*}" ##Works
Sign up to request clarification or add additional context in comments.

Comments

1

You'll find associative arrays much easier to work with.

Unfortunately bash doesn't give you nested arrays, but you can do this:

declare -A MountVolumes=(
    [b]='
        [fs]=xfs
        [mkfs_options]="foo bar baz"
        [mnt_options]="foo bar baz"
        [path]=/foo/bar/baz
    '

    [c]='
        [fs]=xfs
        [mkfs_options]=-"t really -foo /ugly/options"
        [mnt_options]="-t really -foo /ugly/options"
        [path]=/foo/bar/baz
    '
)

for prefix in "${!MountVolumes[@]}"; do
    declare -A "tmp=( ${MountVolumes[$prefix]} )"
    echo "prefix=$prefix, mkfs_options=${tmp[mkfs_options]}"
done

outputs

prefix=c, mkfs_options=-t really -foo /ugly/options
prefix=b, mkfs_options=foo bar baz

I think this is a readable and maintainable way to go. Quoting may become more of an issue.


Since the variables are already in the environment, try this:

for prefix in {b..z}; do 
    if env | grep -q "^MountVolumes_${prefix}_"; then 
        declare -A tmp=()
        for subvar in fs path mkfs_options mnt_options; do 
            var="MountVolumes_${prefix}_${subvar}"
            tmp[$subvar]=${!var}
        done
        echo $prefix
        declare -p tmp
    fi 
done
b
declare -A tmp=([path]="/foo/bar/baz" [fs]="xfs" [mnt_options]="foo bar baz" [mkfs_options]="foo bar baz" )
c
declare -A tmp=([path]="/foo/bar/baz" [fs]="xfs" [mnt_options]="" [mkfs_options]="-t really -foo /ugly/options" )

It would be helpful if the variables were spelled consistently though, not MountVolumes_c_mkfs_fs_options


On the other hand, if you only care about variable names:

prefix=c
tmp="MountVolumes_${prefix}_@"
eval varnames=( "\${!$tmp}" )

which of course is gross, but results in

$ declare -p varnames
declare -a varnames=([0]="MountVolumes_c_fs" [1]="MountVolumes_c_mkfs_fs_options" [2]="MountVolumes_c_mkfs_options" [3]="MountVolumes_c_path")

3 Comments

I do not have control over how the environment is populated. That's what I'm given, I need to find a way to retrieve all vars of the form MountVolumes_b_*
You didn't say the variables were already in the environment. That's quite different. I'll update my answer
"MountVolumes_c_mkfs_fs_options" is actually a real option, the options are optional. Thanks for this. I will definitely come back and scrounge what I can from your answer when its usage becomes relevant.
0

I accepted the answer as it solves the question, but I ended up doing it a slightly different way as has pretty severe security implications with script injection.

Posting it here in case it helps anyone.

#!/bin/bash

MountVolumes_b_mkfs_options='foo bar baz'
MountVolumes_b_path=/foo/bar/baz
MountVolumes_b_mnt_options='foo bar baz'
MountVolumes_b_fs=xfs
MountVolumes_c_path=/foo/bar/baz
MountVolumes_c_fs=xfs
MountVolumes_c_mkfs_fs_options=-'t really -foo /ugly/options'
MountVolumes_c_mkfs_options='-t really -foo /ugly/options'

build_opt() {
  key="${1}_${2}"
  build_opt_res=" --${2} '${!key}'"
}
prefixes=($(echo "${!MountVolumes*}" | grep 'MountVolumes_[b-z]' -o | uniq))

for prefix in ${prefixes[@]}; do
  build_opt "$prefix" "mkfs_options"
  echo "${build_opt_res}" 
  build_opt "$prefix" "path"
  echo "${build_opt_res}" 
done

Stripping out single quotes from ${!key} in build_opt() and hardcoding the known suffixes should remove the possibility of script injection.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.