6

I need to expand some shell (not environment) variable names that are thematically related, e.g. B2_... where ... could be one or more different things like ACCOUNT_ID, ACCOUNT_KEY, RESPOSITORY and so on.

I don't know ahead how many variables there are nor which ones they are. That is what I'm want to find out.

I want to be able to iterate through the B2... variables without having to put each individual name in the list, similar to how I would glob filename expansions.

I use zsh for interactive sessions, but solutions for sh or bash are good too.

  • This is what associative arrays are for, iterate over the array indices. Interactively, I guess you could use tab expansion but that's not iteration, that's just a typing shortcut. – cas Mar 19 '18 at 0:42
  • Do you mean you need to list all variables starting name by B2_ ? – Gilles Quenot Mar 19 '18 at 0:45
  • @GillesQuenot Yes, but but not interactively with <tab>. – Nick Coleman Mar 19 '18 at 1:55
7

Using parameter expansion :

$ foobar_1=x foobar_2=y foobar_3=z
$ for v in "${!foobar_@}"; do echo "$v"; done

Output :

foobar_1
foobar_2
foobar_3

'dereference' :

$ for v in "${!foobar_@}"; do echo "${!v}"; done

Output² :

x
y
z
  • I didn't realise ${!foobar_*} worked like that, so I've learnt something new (20 years in *nix shells too <goes to sit in the shame corner>). And of course ${!v} gives the value too. – Nick Coleman Mar 19 '18 at 6:07
  • 2
    @NickColeman, note that in bash ${!v} and ${!x*} are not related. ${!v} is bash-specific and different from ksh93's ${!v} (where it's to query the target of nameref). ${!x*} comes from ksh93, later copied by bash. It's better to use "${!foobar_@}" than ${!foobar_*} unless you can guarantee that no variable name contains any character also found in the current value of $IFS. – Stéphane Chazelas Mar 19 '18 at 7:22
  • Fixed link for bash PE – Gilles Quenot Mar 19 '18 at 10:38
5

Instead of using individual variables with names beginning with B2_, you could use an associative array (B2). e.g.

Note: the following is specific to zsh. ksh and bash also have associative arrays, but the syntax for initialising and using them is different (see below).

typeset -A B2
# initialise array
B2=(ACCOUNT_ID 12345 ACCOUNT_KEY 54321 REPOSITORY somewhere)

# two different ways of adding elements to the array
B2[FOO]=bar
B2+=(NAME fred)             

for key val in ${(kv)B2}; do 
  echo "$key: $val"
done

Output for that example would be:

ACCOUNT_KEY: 54321
FOO: bar
REPOSITORY: somewhere
ACCOUNT_ID: 12345
NAME: fred

You can also print the entire array (in a form suitable for re-use in a script or on the command line) with typeset -p:

% typeset -p B2
typeset -A B2=( ACCOUNT_ID 12345 ACCOUNT_KEY 54321 FOO bar NAME fred REPOSITORY somewhere )

The same operations in ksh or Bash would be as follows:

# initialise it
typeset -A B2
B2=([ACCOUNT_ID]=12345 [ACCOUNT_KEY]=54321 [REPOSITORY]=somewhere)

# add elements
B2[FOO]=bar
B2+=([NAME]=fred) 

# get a list of the keys and use them as indexes to get the values
for key in "${!B2[@]}"; do 
  echo "$key: ${B2[$key]}"
done

# print the entire array
typeset -p B2
  • Interesting ideas. The variables are already set before my script gets to them; I might see if upstream can change to an array. I like the typeset -p. – Nick Coleman Mar 19 '18 at 6:04
4

Bash has compgen -v.

In Bash, you can list variable names that begin with B2 using:

compgen -v B2

Running the command compgen -v text lists the same variables that are available as autocompletion results when you type $ followed by text and then press tab. (You may have to press tab multiple times for them all to be displayed.) You can use compgen -v interactively, but you can also use it in a script. For example:

$ bash -c 'compgen -v SDK'
SDKMAN_CANDIDATES_DIR
SDKMAN_CURRENT_API
SDKMAN_DIR
SDKMAN_LEGACY_API
SDKMAN_PLATFORM
SDKMAN_VERSION

(The script can, of course, be in a file, rather than passed to bash as an operand to the -c flag.)

The purpose of compgen is to perform command completions. See the output of help compgen (in Bash) for general information about it. Although this will probably do what you want, you might find that other approaches, such as the parameter expansion technique in Gilles Quenot's answer, more clearly express what you're doing. I can't really say which is better, as this depends on what you are doing and perhaps also on your personal stylistic preferences.

  • Great use of compgen, I'm guilty of thinking of all the comp... functions as relevant to interactive completion functions only. – Nick Coleman Mar 19 '18 at 6:10
2

With zsh:

for var (${(M)${(k)parameters}:#B2_*})
  printf '$%s == %s\n' $var ${(P)var}
  • ${(k)parameters} are the keys of the $parameters special associative array that gives information on all parameters.
  • ${array:#B2_*} removes from the $array expansion, the elements that match B2_*. With (M) (for Matched), that's reversed: keep the elements that match.
  • ${(P)var} (similar to bash's ${!var}): expands to the value of the parameter whose name is stored in $var.

If you want only the variables that are not exported:

setopt extendedglob # for ^
for var (${(M)${(k)parameters[(R)^*export*]}:#B2_*})
  printf '$%s == %s\n' $var ${(P)var}

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.