0

I want to create a nested associative array in bash and populate it in a loop. Here is the sample code that should print all the file names along with the corresponding Last Modification Time of that file in the current directory.

declare -A file_map
for file in *
do
    declare -A file_attr
    uuid=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)
    file_attr['name']="$file"
    file_attr['mod_date']=$(stat -c %y "$file")
    file_map["$uuid"]=file_attr
done


for key in "${!file_map[@]}"
do
    echo $(eval echo \${${file_map[$key]}['name']}) "->" $(eval echo \${${file_map[$key]}['mod_date']})
done

But it's only printing the information for a single file in the directory.

The output is coming as:

test.sh -> 2017-03-10 18:46:52.832356165 +0530
test.sh -> 2017-03-10 18:46:52.832356165 +0530
test.sh -> 2017-03-10 18:46:52.832356165 +0530
test.sh -> 2017-03-10 18:46:52.832356165 +0530

Whereas it should be, test.sh and 3 other different files.

Seems like declare -A file_attr is not creating any new associative array and as a result the previous values are getting overwritten. Any hep ?

2
  • a{k1}.b{k2} could be represented as x{k1.k2}. Isn't the flattening of the hash easier? Commented Mar 20, 2017 at 6:37
  • Only a function creates a new scope in shell, and file_map["$uuid"]=file_attr just assigns adds the string "file_attr", not an array reference, to the array. I strongly recommend using a language with proper data structures instead of bash. (As an additional benefit, you'l probably get a library that wraps the stat system call instead of having to run an external program for every file.) Commented Mar 20, 2017 at 12:04

1 Answer 1

1

You need to create a distinct, global array in each loop iteration, and store its name in the "outer" array. You'll need declare to assign to the dynamically generated arrays as well.

declare -A file_map
for file in *
do
    declare -A file_attr$((i++))
    # Consider using something like uuidgen instead
    # uuid=$(uuidgen)
    uuid=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)
    declare -A "file_attr$i[name]=$file"
    declare -A "file_attr$i[mod_date]=$(stat -c %y "$file")"
    file_map[$uuid]="file_attr$i"
done

Use indirect variable expansion, not `eval`, to access the elements.

for uuid in "${!file_map[@]}"
do
    arr=${file_map[$uuid]}
    file_map_name=$arr[name]
    file_map_date=$arr[mod_date]
    echo "$uuid: ${!file_map_name} -> ${!file_map_date}"
done
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.