8
[sudhir.tataraju.WBLL0717E4418] ➤ cat test.sh

#!bin/bash

i=2

value2=abc

echo "`$value$i`"

Output:

[sudhir.tataraju.WBLL0717E4418] ➤ sh test.sh
test.sh: line 8: 2: command not found

Debug output:

[sudhir.tataraju.WBLL0717E4418] ➤ sh -x test.sh
+ i=2
+ value2=abc
++ 2
test.sh: line 8: 2: command not found
+ echo ''

How to get output abc without error?

James Z
  • 12,209
  • 10
  • 24
  • 44
sudhir tataraju
  • 1,159
  • 1
  • 14
  • 30
  • 1
    `eval echo "\$value$i"` – Cyrus Dec 31 '17 at 11:23
  • A couple of asides: is `#!bin/bash` the first line in your file or the second? Also, you should have `#!/bin/bash` for the absolute path. – lurker Dec 31 '17 at 12:13
  • Seems to me that you are just looking for the functionality of arrays, for example: `i=2; arr=(abc def ghi); echo "${arr[i]}"`. – PesaThe Dec 31 '17 at 12:26
  • 1
    `sh -x test.sh` is not running `test.sh` with bash, it's running it as a POSIX sh script. Use `bash -x test.sh` (and consider [leaving the `.sh` extension off entirely](https://www.talisman.org/~erlkonig/documents/commandname-extensions-considered-harmful/), as implying that a bash script can be run with `/bin/sh` is actively misleading, and using extensions for command names is contrary to UNIX convention -- you don't run `ls.elf`). – Charles Duffy Dec 31 '17 at 18:43
  • (...so, to be clear, when you use `sh` manually, you're causing the `#!/bin/bash` shebang to be ignored). – Charles Duffy Dec 31 '17 at 18:52

5 Answers5

6

What you are trying to do can be achieved much better with arrays.

values=( value1 value2 value3 "This is value 4" )
echo "${values[2]}"
values[2]="other_value"
echo "${values[2]}"

Variables can also be used as the index:

i=2
echo "${values[$i]}"

Arrays make looping over the values very easy:

#looping over values
for value in "${values[@]}"; do
    echo "$value"
done
#looping over indexes
for index in "${!values[@]}"; do
    echo "${values[$index]}"
done

If you don't consider sparse or associative arrays, you can also loop like this:

for (( index=0; index<${#values[@]}; index++ )); do
    echo "${values[index]}"
done

Arrays are the right structure for your task. They offer great functionality. For more information on arrays, see: wiki.bash-hackers.org/syntax/arrays and Bash Reference Manual.

PesaThe
  • 7,259
  • 1
  • 19
  • 43
  • I'm split -- on one hand, it's a good answer; on the other hand, it's an answer to a FAQ we see re-asked at least monthly. – Charles Duffy Dec 31 '17 at 18:53
  • @CharlesDuffy True. This answer is pretty frequent. However, so is the question and other answers. I am still relatively new here so if you think deletion of this answer/some editing is appropriate, please tell me so :) – PesaThe Dec 31 '17 at 19:05
  • 1
    Heh. In hindsight, it probably would have been appropriate for the regulars -- the folks who knew better -- to jump on the close vote more quickly. Now that there are bad answers present, though, withdrawing the good ones would do more harm than good. – Charles Duffy Dec 31 '17 at 19:08
5

What you're asking for can be achieved in two ways (without antipatterns or eval).

  1. Use indirect expansion:

    The basic form of parameter expansion is ${parameter}. The value of parameter is substituted. [...] If the first character of parameter is an exclamation point (!), [...] it introduces a level of variable indirection. Bash uses the value of the variable formed from the rest of parameter as the name of the variable; this variable is then expanded and that value is used in the rest of the substitution, rather than the value of parameter itself. This is known as indirect expansion.

    So what you want is:

    #!/bin/bash
    
    i=2
    
    value2=abc
    
    # Have a variable that expands to the name of the variable you want
    myvar=value$i
    
    # Use indirect expansion
    echo "${!myvar}"
    
  2. Realize that your design is very bad, and use some proper structure instead: Bash handles arrays. See PesaThe's answer. That's definitely the recommended method in your case.


Don't use eval. Though it might look great, you need a good deal of practice of shell scripting before you understand all the problems with eval. And when you have enough experience with Bash, you'll realize that you very very rarely need eval.

gniourf_gniourf
  • 44,650
  • 9
  • 93
  • 104
  • 1
    I'm split -- on one hand, it's a good answer; on the other hand, it's an answer to a FAQ we see re-asked at least monthly. – Charles Duffy Dec 31 '17 at 18:55
0

Use eval:

eval echo "\$value$i"

From help eval:

eval: eval [arg ...]  
   Execute arguments as a shell command.

   Combine ARGs into a single string, use the result as input to the shell,  
   and execute the resulting commands.

So the command is first expanded to

eval echo "$value2"

Note the first dollar is escaped so it isn't expanded as $value

Then eval takes effect: The whole command is evaluated again:

echo "abc"

Output, as desired: abc

iBug
  • 35,554
  • 7
  • 89
  • 134
  • 1
    `eval` should not be suggested to non-experts (except in very very specific cases, and this isn't one). – gniourf_gniourf Dec 31 '17 at 13:12
  • @gniourf_gniourf Fine. – iBug Dec 31 '17 at 13:22
  • 2
    To clarify the "why" of that -- using `eval` creates [substantial security concerns](http://mywiki.wooledge.org/BashFAQ/048), and with bash having built-in indirect expansion mechanisms, it's [completely unnecessary](http://mywiki.wooledge.org/BashFAQ/006#Evaluating_indirect.2Freference_variables). – Charles Duffy Dec 31 '17 at 18:40
0

Another solution, using nameref:

i=2
value2="abc"
declare -n essai=value"$i"
echo "$essai"
PesaThe
  • 7,259
  • 1
  • 19
  • 43
ctac_
  • 2,413
  • 2
  • 7
  • 17
-2

Here is a complete example that works

#!/bin/bash

val1='foo'
val2='bar' 
val3='baz'

for i in {1..3}
do
  eval echo "\$val${i}"
done
opentokix
  • 843
  • 1
  • 5
  • 11