Dale
Dale

Reputation: 1308

Bash variable expansion with variable

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?

Upvotes: 0

Views: 120

Answers (3)

Dale
Dale

Reputation: 1308

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.

Upvotes: 0

Philippe
Philippe

Reputation: 26447

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

Upvotes: 2

glenn jackman
glenn jackman

Reputation: 246754

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

Upvotes: 1

Related Questions