4

So I'm trying to do something, not sure if it's possible. I have the following code:

for i in {0..5}; do
    if [[ -f ./user$i ]]; then
        group$i=$(grep -w "group" ./user0|awk '{print $2}'|perl -lape 's/\s+//sg')

What I want to do is assign a unique variable for each instance of the {0..5} so group1 group2 group3 group4 for each variable name. Then I would change ./user0 to ./user$i and create a dynamic list of variables based on my sequence. Is this possible? I get the following error when trying to execute this and I'm unsure of what I have actually done that bash doesn't like.

test.sh: line 16: group0=j: command not found

2
  • 2
    I recommend using bash arrays instead of dynamic variable names if possible. Commented Feb 24, 2016 at 2:54
  • @KurtStutsman -- I was thinking that as well Commented Feb 24, 2016 at 3:04

3 Answers 3

5

Kurt Stutsman provides the right pointer in a comment on the question: use Bash arrays to solve your problem.

Here's a simplified example:

groups=() # declare an empty array; same as: declare -a groups
for i in {0..5}; do
  groups[i]="group $i"  # dynamically create element with index $i
done

# Print the resulting array's elements.
printf '%s\n' "${groups[@]}"

See the bottom of this answer for other ways to enumerate the elements of array ${groups[@]}.

  • bash arrays can be dynamically expanded (and can even be sparse - element indices need not be contiguous)

    • Hence, simply assigning to element $i works, without prior sizing of the array.
  • Note how $i need not be prefixed with $ in the array subscript, because array subscripts are evaluated in an arithmetic context (the same context in which $(( ... )) expressions are evaluated).


As for what you did wrong:

group$i=...

is not recognized as a variable assignment by Bash, because - taken literally - group$i is not a valid identifier (variable name).

Because it isn't, Bash continues to parse until the next shell metacharacter is found, and then interprets the resulting word as a command to execute, which in your case resulted in error message group0=j: command not found.


If, for some reason, you don't want to use arrays to avoid this problem entirely, you can work around the problem:

By involving a variable-declaring builtin [command] such as declare, local, or export, you force Bash to perform expansions first, which expands group$i to a valid variable name before passing it to the builtin.

  • user2683246's answer demonstrates the next best approach by using declare (or, if local variables inside a function are desired, local) to create the variables.

  • Soren's answer uses export, but that is only advisable if you want to create environment variables visible to child processes rather than mere shell variables.

Caveat: With this technique, be sure to double-quote the RHS in order to capture the full value; to illustrate:

 i=0; declare v$i=$(echo 'hi, there'); echo "$v0" # !! WRONG -> 'hi,': only UP TO 1ST SPACE

 i=0; declare v$i="$(echo 'hi, there')"; echo "$v0" # OK -> 'hi, there'

Other ways to enumerate the groups array created above:

# Enumerate array elements directly.
for element in "${groups[@]}"; do
  echo "$element"
done

# Enumerate array elements by index.
for (( i = 0; i < ${#groups[@]}; i++ )); do
  echo "#$i: ${groups[i]}"
done
Sign up to request clarification or add additional context in comments.

5 Comments

Ok that makes sense. But I'm having trouble understanding how to assign the values to my other variables that I provided above. I understand how your example works but how do I apply that to my group=$(command) syntax?
Oh nvmnd. I got it working. I don't fully understand why this works though. group[i]="group$i" group[i]=$(grep -w "group" ./user$i|awk '{print $2}'|perl -lape 's/\s+//sg')
So this does work, but what about the rest of my problem as edited above? How do I write each value to a file and have it be unique? My Echo statement does not work when attempting this with ${group[@]}. Obviously if I change it to [1] it will work but how do I do it for each variable dynamically?
Oh I think I just figured it out. It would be ${group[$i]}.
Yes, though as explained in the answer, "${groups[i]}" (no $ sigil needed) will do - also, be sure to double-quote variable references (whether arrays, array elements, or scalars), unless you want to subject them to word splitting and pathname expansion by the shell.
4

Use declare group$i=... instead of just group$i=...

Comments

1

Try to use the export or declare function like this

for i in {0..5}; do
    if [[ -f ./user$i ]]; then
        export group$i=$(grep -w "group" ......

with declare

for i in {0..5}; do
    if [[ -f ./user$i ]]; then
        declare group$i=$(grep -w "group" ......

where export makes the value available to sub-processes, and declare just available within the same script.

7 Comments

Oh man. PERFECT. Worked 100%. Do you mind explaining to me WHY this worked? I am reading up on the export command but I don't fully understand why this works.
Updated my code above with another question for you. This works but I ran into another issue.
You should ask a new question rather than updating
There is no good reason to use export, unless you need to create environment variables visible to child processes. In Bash, the right choice is to use arrays; the next best option is to use declare (or local), as demonstrated in @user2683246's answer.
Aside from that: unless you double-quote the RHS, only its value up to the first space will be captured, so it should be export group$i="$(...)".
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.