I'm trying to list all the variables with a certain prefix, but that prefix is dynamically determined. For example:


If I simply do:

echo ${!apple_@}

I can get the variable names that start with apple_. However, I would like to do this by using the variable prefix somehow. Everything I've tried leads to a bad substitution. For example I cannot:

echo ${!${prefix}@}
  • 1
    Depending on your exact problem, you might want to consider using arrays. – Sparhawk Nov 27 '15 at 23:12
  • @Sparhawk I was thinking the same thing a little while after I submitted the question. Am I really trying to reinvent the wheel? – Angelo Nov 28 '15 at 1:35
  • I only mention it because I've tried to do the same thing. I refactored to use arrays, and even though I had to modify what I was doing conceptually, it made more "sense" in the end. And seemed less hacky. – Sparhawk Nov 28 '15 at 1:38
  • I had been trying to avoid associative arrays because I was burned a little while back due to portability issues. (I think they started support for that in Bash 4.) – Angelo Nov 28 '15 at 1:43
  • I have this same requirement, with the additional stipulation that I be able to export the variables, so bash arrays are excluded. – philwalk Nov 16 '18 at 21:24

Bash doesn't parse nested variable expansions. You can do what you want with eval: build the shell snippet ${!apple_@}, then use it in a command that you execute with eval.

eval 'vars=(${!'"$prefix"'@})'

Make sure that prefix contains only valid identifier characters, otherwise the snippet could result in anything.

eval is usually necessary to work with variables whose name is calculated dynamically, but in this specific case there's a completely different way to do the same thing: use the completion system. Bash's completion system works even from a script. The compgen builtin lists one completion per line, which is ambiguous when completions can contain newlines, but this isn't the case here — they don't even contain wildcard characters, so the output can be split with a simple unquoted expansion. This will safely output nothing if the prefix contains characters that aren't valid in identifiers.

vars=($(compgen -v "$prefix"))

You could use env | grep '^prefix'.

For example:

$ env | grep '^LESS'
LESSOPEN=| /usr/bin/lesspipe %s
LESSCLOSE=/usr/bin/lesspipe %s %s

If you only want the values and not the variable names, use awk:

$ env | awk -F= '/^LESS/ {print $2}'
| /usr/bin/lesspipe %s
/usr/bin/lesspipe %s %s

(or print $1 for just the variable names)

Update 2019-05-17

I was brought back to this answer by an upvote, and realised there's an obvious improvement:

typeset -p works better than env, it outputs all variables whether they're exported or not. For example:

typeset -p | awk '$3 ~ /^apple_/ { print $3 }'

To use a variable rather than a fixed string:

typeset -p | awk '$3 ~ "^"pfx { print $3 }' pfx="$prefix" 

or just export $prefix so it's available in awk's ENVIRON array:

export prefix=apple_
typeset -p | awk '$3 ~ "^"ENVIRON["prefix"] { print $3 }'

Note: with a little work, this can be made to work with other shells such as ksh or zsh, but it is important to note that the output format for typeset -p is different in other shells, so the awk script will have to be modified to suit them.

  • 1
    Not if the variables aren't exported. And this can break if there are environment variables containing line breaks. – Gilles Nov 27 '15 at 22:42
  • true. nothing's perfect, and this is about as close as it's possible to get to what the OP wanted. set could also be used if you don't mind getting shell functions mixed in with your variables. – cas Nov 27 '15 at 22:47
  • well, it wont put the function names out if you tell it to behave: set -o posix; set – mikeserv Nov 27 '15 at 23:06
  • @cas It's possible to get exactly what the OP wanted. It would be difficult if you wanted a POSIX solution, but ${!prefix} is bash-specific in the first place (it isn't even a ksh feature). – Gilles Nov 27 '15 at 23:10
  • typeset -p works better than env, outputs all variables whether they're exported or not. e.g. typeset -p | awk '$3 ~ /^BASH/ {print $3}', and avoids the potentially dangerous use of eval. I should have remembered typeset when I first posted this answer. to a variable rather than a fixed string: prefix="BASH"; typeset -p | awk '$3 ~ "^"prefix { print $3}' prefix="$prefix" – cas May 17 at 11:55
    eval "printf %b $(#"
          set -- "${1##[0-9]*}" _\[:alnum:]
          [ -z   "${1##*[!$2]*}"  ]|| set |
          sed -ne"s/^\($1[$2]*\)=.*/"'"${\1+\1\\n}" \\/p')
"; }; #"

apple_one="a single apple"
apple_two="an apple and an
apple_three='is not actually set at all'

preset apple_


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.