Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bash: Use a variable as an array name

Tags:

bash

I am parsing a log file and creating associative arrays for each user with the line number and the last field (total time logged in). The lines of the log file look like this:

jww3321   pts/2        cpe-76-180-64-22 Mon Oct 18 23:29 - 00:27  (00:58)
jpd8635   pts/1        cpe-74-67-131-24 Mon Oct 18 23:22 - 03:49  (04:26)

Where the first field (jww3321) will be the array name and the first entry in the array will be (1,00:58), the next will be (2,(the next time for user)). In order to obtain the proper keys I need to save the the length of list and add one to it when I add the next value to a user array. My code so far looks like this:

cat lastinfo.txt | while read line
do
    uname=`echo "$line" | awk '{print $1;}'`
    count=`echo "${#$uname[@]}"`
    echo "$count"
done

I have tried using indirect references but I am stuck with this error:

l8t1: line 7: ${#$uname[@]}: bad substitution

Any suggestions?

like image 969
user1034850 Avatar asked Nov 08 '11 03:11

user1034850


People also ask

How do you use a variable as part of an array name?

Instead of having so many similar variables, you should use an array, or in your case an array of arrays: var myVariableArray = new double[][] { c[0], c[1], ... }; Now you can easily acces the i-th number within that array: double a = myVariableArray[i][i];

Can a variable be an array?

An array is a variable containing multiple values. Any variable may be used as an array. There is no maximum limit to the size of an array, nor any requirement that member variables be indexed or assigned contiguously.

How do I echo an array in bash?

How to Echo a Bash Array? To echo an array, use the format echo ${Array[0]}. Array is your array name, and 0 is the index or the key if you are echoing an associative array. You can also use @ or * symbols instead of an index to print the entire array.


Video Answer


2 Answers

I'm not sure if I understood correctly what you are trying to do, specifically the "associative" part (I can't see where an associative array is used), but this code does what I UNDERSTAND that you want to do:

#!/bin/bash
while IFS=" " read user time; do
    eval "item=\${#$user[@]} ; $user[\$item]=\(\$((\$item + 1)),$time\)"
    [[ "${arraynames[@]}" =~ $user ]] || arraynames[${#arraynames[@]}]=$user
done< <(sed -r 's/^ *([[:alnum:]]*) .*\((.*)\)$/\1 \2/')

for arrayname in ${arraynames[@]}; do
    eval "array=(\${$arrayname[@]})"
    echo "$arrayname has ${#array[@]} entries:"
    for item in ${!array[@]}; do
        echo "$arrayname[$item] = ${array[$item]}"
    done
    echo
done

It reads from stdin. I've tested it with an example file like this:

    jww3321   pts/2        cpe-76-180-64-22 Mon Oct 18 23:29 - 00:27  (00:58)
    jpd8635   pts/1        cpe-74-67-131-24 Mon Oct 18 23:22 - 03:49  (04:26)
    jww3321   pts/2        cpe-76-180-64-22 Mon Oct 18 23:29 - 00:27  (01:58)
    jpd8635   pts/1        cpe-74-67-131-24 Mon Oct 18 23:22 - 03:49  (05:26)

Output:

    jww3321 has 2 entries:
    jww3321[0] = (1,00:58)
    jww3321[1] = (2,01:58)

    jpd8635 has 2 entries:
    jpd8635[0] = (1,04:26)
    jpd8635[1] = (2,05:26)

Note that only standard integer-indexed arrays are used. In Bash, as of now, indirect array references in the left side always involve using eval (uuuuuuhhhh, ghostly sound), in the right side you can get away with ${!} substitution and command substitution $().

Rule of thumb with eval: escape what you want to be expanded at eval time, and don't escape what you want to be expanded before eval time. Any time you're in doubt about what ends up being eval'd, make a copy of the line and change eval for echo.

edit: to answer sarnold's comment, a way to do this without eval:

#!/bin/bash
while IFS=" " read user time; do
    array=$user[@] array=( ${!array} ) item=${#array[@]}
    read $user[$item] <<< "\($(($item + 1)),$time\)"
    [[ "${arraynames[@]}" =~ $user ]] || arraynames[${#arraynames[@]}]=$user
done< <(sed -r 's/^ *([[:alnum:]]*) .*\((.*)\)$/\1 \2/')

for arrayname in ${arraynames[@]}; do
    array=$arrayname[@] array=( ${!array} )
    echo "$arrayname has ${#array[@]} entries:"
    for item in ${!array[@]}; do
        echo "$arrayname[$item] = ${array[$item]}"
    done
    echo
done
like image 184
ata Avatar answered Oct 14 '22 02:10

ata


You are not creating associative arrays. The error is related to the syntax of ${#$uname[@]}: delete the second dollar sign.

like image 43
choroba Avatar answered Oct 14 '22 02:10

choroba