0

This question already has an answer here:

I declared var0, var1, var2, var3 with a for loop. How do I echo the vars inside that for loop? Here's the code.

#!/bin/bash

for i in {0..3}
do
  export var$i=$i
done;

There i defined var0, var1, var2, var3.. how do i access them in a loop?

i tried the following

for i in {0..3}
do
  echo $var$i
  tmpvar=var$i
  echo $tmpvar
done

but none gave me the values of var0,var1... first echo just printed '0,1,2,3', second echo printed 'var0,var1,var2,var3' what do i do? I want the values..

marked as duplicate by Jeff Schaller, RalfFriedl, Romeo Ninov, Thomas, Luciano Andress Martini Sep 17 '18 at 20:50

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • 3
    it may be more sensible to use an array rather than trying to use variables as variable names – thrig Sep 16 '18 at 17:01
  • 5
    I believe it is called "Indirect variable reference". Possible duplicate of Use a variable reference "inside" another variable – user56041 Sep 16 '18 at 21:16
4

You can use eval for that:

$ var1='< 1 >'; var2='< 2 >'; var3='< 3 >'
$ for i in 1 2 3; do eval echo \$var$i; done
$ for i in 1 2 3; do eval "echo \$var$i"; done
$ for i in 1 2 3; do eval 'echo $var'$i; done
$ for i in 1 2 3; do eval "v=\$var$i"; echo "$v"; done

Take care with the quoting and escaping. If the values contain whitespace or glob characters, the first three will split the values on whitespace, and then expand any filenames that match the glob. This may or may not be what you want. For example:

$ var1='<  x  >' var2='< * >'
$ for i in 1 2; do eval echo \$var$i; done
< x >
< [list of filenames in current directory] >

(Note that the double spaces around x were collapsed to single spaces since echo received <, x and > as separate arguments.)

To work around that, you need to make sure the variable expansion is quoted within the eval, e.g.:

$ for i in 1 2; do eval echo \"\$var"$i"\"; done
<  x  >
< * >

(quote the $i too, as above, unless you know IFS doesn't contain any digits.)

  • 2
    Leaving those variables unquoted is invoking the split+glob operator which you don't want here. See for instance Security implications of forgetting to quote a variable in bash/POSIX shells. Try with IFS=123 or var1='*' for instance. – Stéphane Chazelas Sep 16 '18 at 22:02
  • The IFS splitting and globbing is not some kind of bug, but useful and well-documented behavior, maybe I really want it?. These examples are meant to illustrate how "eval" works, and the quote clutter stays in the way and gives the false impression that it guards against actually executing commands from var1, etc. Anyways, I made the last example split+glob immune. – mosvy Sep 16 '18 at 22:48
  • 3
    @mosvy, It's not about preventing the command execution, it's about keeping the values of the variables intact. Also, it's not about what you want, it's about what the people reading this answer want. And just out experience from reading the questions here, most of the time they don't want to split'n'glob when using variables. Here, you chose the variables very carefully so that echo will output them seemingly intact even after splitting (with the default IFS) and globbing. But just something like <␣␣1␣␣> or <␣*␣> would not stay as-is. – ilkkachu Sep 17 '18 at 8:26
  • @ilkkachu, the $i is still unquoted in your edit so subject to split+glob (which means that code can't be used in contexts where $IFS may have been modified), see my original edit (which mosvy reverted). – Stéphane Chazelas Sep 17 '18 at 8:57
  • 2
    Leaving a parameter unquoted when you don't want split+glob is bad coding practice. As far as possible we should avoid bad coding practice in code posted in answers. – Stéphane Chazelas Sep 17 '18 at 9:01
5

Given,

var0=a
var1=b
var2=c

with ksh93 or bash 4.3 or newer, you can use a nameref to point at the individual variables:

for i in 0 1 2; do
    typeset -n p="var$i"
    echo "$p"
done

But unless you explicitly need the variables as separate scalars (for example because you passed them through the environment), and are not using a strictly standard shell, but Bash or ksh, you should use an array instead:

#!/bin/bash
a=(a b c)
for i in 0 1 2; do
    echo "${a[i]}"
done

Or ignoring the indexes:

for x in "${a[@]}"; do
    echo "$x"
done

(Zsh, of course, has arrays too, it just starts indexing from 1 by default so the first one doesn't work directly.)

  • No reproach here, answer is correct, given OP specified the bash tag, so arrays are legit. However (and it's a mere opinion) I would try to stay away from the array cookie-jar for portability reasons. So the last incarnation of @mosvy's answer (incorporating Stéphane's comment) is definitely a go if you work with many different *nix-centric OSes. – Cbhihe Sep 17 '18 at 7:34
  • @Cbhihe, yeah, it depends of course. Arrays are seriously useful if one needs to deal with, well, array-like data in the shell. I would be tempted to look into compiling Bash or ksh if I had to do that on a system with just plain sh... – ilkkachu Sep 17 '18 at 8:34
  • Note that it's not just zsh that has arrays starting at 1. It's all shells but ksh and bash (including Bourne/POSIX ("$@"), csh, yash, fish, rc, es...). In zsh, you can set the ksharrays option if you want then to start at 0. – Stéphane Chazelas Sep 17 '18 at 9:09

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