Bash- populate an associative array using a loop - arrays

So I have this to be entered into and associative array:
47 SPRINGGREEN2
48 SPRINGGREEN1
49 MEDIUMSPRINGGREEN
50 CYAN2
51 CYAN1
52 DARKRED
53 DEEPPINK4
It's part of a bash script.
I'm looking for a way to make an associative array out of this, so it would look like
declare -A cols=( [SPRINGGREEN2]="0;47"...[DEEPPINK4]="0;53" )
I can do that quite easily manually.
But I want to use a for loop to populate the array cols=( [KEY]="vALUE" )
For loop will take 47, 48, 49...53 and out it into VALUE field,
and SPRINGGREEN2...DEEPPINK4 into the key field.
I was thinking using awk but couldn't figure our how to isolate the two fields and use each entry to populate the array.

Are you intending to read from the file and populate the cols array?
declare -a cols
while read num color; do
cols[$num]=$color
done < file.txt
for key in "${!cols[#]}"; do printf "%s\t%s\n" "$key" "${cols[$key]}"; done
Oh the other hand, if you already have the associative array and you also want a "reversed" array:
declare -a rev_cols
for color in "${!cols[#]}"; do
rev_cols[${cols[$color]#*;}]=$color
done
for key in "${!rev_cols[#]}"; do printf "%s\t%s\n" "$key" "${rev_cols[$key]}"; done

Related

BASH:Sort the array and put sorted keys into another array [duplicate]

So I am quite struggling with arrays in shell scripting, especially dealing with sorting the key values. Here's what I have
declare -A array
Array[0]=(0)
Array[1]=(4)
Array[2]=(6)
Array[3]=(1)
So in each array we have (0,4,6,1), if we sort them to the largest to the smallest, it would be (6,4,1,0). Now, I wonder if I could sort the key of the value, and put them in a new array like this(sort of like ranking them):
newArray[0]=(2) # 2 which was the key for 6
newArray[1]=(1) # 1 which was the key for 4
newArray[2]=(3) # 3 which was the key for 1
newArray[3]=(0) # 0 which was the key for 0
I've tried some solutions but they are so much hard coded and not working for some situations. Any helps would be appreciated.
Create a tuple of index+value.
Sort over value.
Remove values.
Read into an array.
array=(0 4 6 1)
tmp=$(
# for every index in the array
for ((i = 0; i < ${#array[#]}; ++i)); do
# output the index, space, an array value on every line
echo "$i ${array[i]}"
done |
# sort lines using Key as second column Numeric Reverse
sort -k2nr |
# using space as Delimiter, extract first Field from each line
cut -d' ' -f1
)
# Load tmp into an array separated by newlines.
readarray -t newArray <<<"$tmp"
# output
declare -p newArray
outputs:
declare -a newArray=([0]="2" [1]="1" [2]="3" [3]="0")

How to use a variable's value as identifier in order to declare/modify an array in BASH?

I'll try to explain:
I'm writing a bash script and I'm within a for loop. For all loops, I got a variable VAR_ID, that is used to store an identifier for another variable that will be exported when all work is done. For each single loop, I got a variable VAL. VAL's value should be assigned to VAR_ID's value. In the end, VAR_ID's value has to be an array in order to store all values.
Phew... well, it's a bit hard to explain for me, but I think this simple script should clarify what I'm trying to do (it's narrowed down from its actual purpose of course):
#!/bin/bash
COUNT=0
VAR_ID="my_array"
declare -a "$VAR_ID"
while (( $COUNT <= 2 ))
do
VAL=$RANDOM
$VAR_ID+=( "$VAL" ) # this doesn't work
(( COUNT++ ))
done
export $VAR_ID
This should result in a variable my_array and three random values in it. I tried using declare as in declare $VAR_ID=$VAL, but this doesn't work anymore if I use += instead of =.
COUNT can be used in a possible solution as position number or so if it helps as I have it also in my original script.
Thanks for any help in advance
Edit: I know there is a possibility with eval but I try to avoid using that until there is no other way.
I'm not sure what you're trying to do exactly, but you could use an associative array and export that. You can access elements of an associative array with variables in bash.
#!/usr/bin/env bash
declare -Ax arr # declare and export an associative array named 'arr'
declare -x var_id='array_position' # declare and export a variable named 'var_id'
for((i=0; i<=2; i++)); do
val=${RANDOM}
arr["${var_id}"]="${val}" # assign to the associative array with the var_id variable being the key, and val being the value
done
You can then access the associative array with variables, strings, or by iterating through it.
# Using an exact key name
echo "${arr[array_position]}"
# Using a variable which points to the key name
echo "${arr[${var_id}]}"
# Using a for loop
for key in "${!arr[#]}"; do
value="${arr[${key}]}"
echo "${key} ${value}"
done
From the bash manpage
When assigning to an associative array, the words in a compound
assignment may be either assignment statements, for which the
subscript is required, or a list of words that is interpreted as a
sequence of alternating keys and values: name=(key1 value1 key2 value2
… ). These are treated identically to name=( [key1]=value1
[key2]=value2 … ). The first word in the list determines how the
remaining words are interpreted; all assignments in a list must be of
the same type. When using key/value pairs, the keys may not be missing
or empty; a final missing value is treated like the empty string.
This syntax is also accepted by the declare builtin. Individual array
elements may be assigned to using the name[subscript]=value syntax
introduced above.
Here is an answer for my own question for anyone who encounters the same problem:
#!/bin/bash
COUNT=0
VAR_ID="my_array"
declare PSEUDOARRAY # is actually just a list of strings separated by spaces
while (( $COUNT <= 2 ))
do
VAL=$RANDOM
PSEUDOARRAY+="$VAL "
(( COUNT++ ))
done
declare -agx $VAR_ID='( $PSEUDOARRAY )' # "cast" PSEUDOARRAY on an actual array
This solves my problem. Note that if you don't do the "cast" part at the end an just use VAR_ID directly, the resulting my_array doesn't provide things like ${my_array[0]} to give only the first item or ${#my_array[#]} to count the items.
Also note: This method is limited as it does not distinguish between separator spaces and "value spaces" that may be stored in VAL.

Inconsistencies when removing elements from a Bash array

When using bash arrays, you can remove an element in the following manner:
unset array[i] # where i is the array index
The problem with this is after the unset ${array[#]] is not truly valid. Yes
it does give you the number of active array elements, but not the actual array
depth. The index of the removed index in the array still exists. It simply has
been set inactive/null. For example:
declare -a array=( a b c d e )
unset array[2]
for ((i=0; i < ${#array[#]}; i++)) ; do
echo "$i: ${array[$i]}"
done
outputs the following:
0: a
1: b
2:
3: d
array[2] is still there but set to null or inactive
array[4] (e) does not show because ${#array[#]} is the number of active
array elements and not the true array element depth. This gets very messy very
quickly each time an element is unset
As an example consider the following code:
# array contains 0 or more strings
# remove looks for and removes a given string
remove () {
local str=$1
for (( i = 0 ; i < ${#array[#]}; i++ )) ; do
if [[ "${array[$i]}" == "$str" ]] ; then
unset array[$i]
return 0
fi
done
echo "$str: not registered"
return 0
}
This is only valid the first time remove is called. After that, valid
elements may be missed.
One fix for this is to added the following line after the unset:
unset array[$i]
+ array=( "${array[#]}" )
This re-initializes array with the element completely removed.
The problem, this feels kludgy.
So my questions are this:
1) is there a way of getting the true array element depth?
2) is there a way of detecting the end of an array when iterating through it?
3) is there another cleaner solution?
Understanding Why for ((i=0; i<${#array[#]}; i++)) is broken
There's no such thing as "true array element depth" in the sense that you're asking for here. Bash arrays aren't really arrays -- they're hash maps (numerically indexed for regular arrays, indexed by strings for bash 4.0's new "associative arrays"). As such, there's absolutely no reason for the keys to start from 0 -- you can have an array like the following: declare -a array=( [1000]="one thousand" [2000]="two thousand" [3000]="three thousand" ), and its length is exactly 3; there aren't a bunch of NUL/empty elements sitting between those items (looked up with keys 1000, 2000 and 3000, respectively).
Removing Elements From A Sparse Array Safely
Iterate over indices, if you want to remove by index. Whereas "${array[#]}" iterates over items in the array, "${!array[#]}" (note the !) iterates over by the indices by which those items can be looked up.
As you've observed, it's unsafe to assume that the indices range from 0 to the total number of items in an array, as bash arrays are allowed to be sparse -- but there's no reason to write your code to make that assumption in the first place.
for array_idx in "${!array[#]}"; do
unset "array[$array_idx]"
done

Parse array based on variable and nth character

Looking to be able to parse an array based on a variable and take the next 2 characters
array=( 7501 7302 8403 9904 )
if var = 73, result desired is 02
if var = 75, result desired is 01
if var = 84, result desired is 03
if var = 99, result desired is 04
Sorry if this is an elementary question, but I've tried variations of cut and grep and cannot find the solution.
Any help is greatly appreciated.
You can use this search function using printf and awk:
srch() {
printf "%s\n" "${array[#]}" | awk -v s="$1" 'substr($1, 1, 2) == s{
print substr($1, 3)}' ;
}
Then use it as:
srch 75
01
srch 73
02
srch 84
03
srch 99
04
Since bash arrays are sparse, even in older versions of bash that don't have associative arrays (mapping arbitrary strings as keys), you could have a regular array that has keys only for numeric indexes that you wish to map. Consider the following code, which takes your input array and generates an output array of that form:
array=( 7501 7302 8403 9904 )
replacements=( ) # create an empty array to map source to dest
for arg in "${array[#]}"; do # for each entry in our array...
replacements[${arg:0:2}]=${arg:2} # map the first two characters to the remainder.
done
This will create an array that looks like (if you ran declare -p replacements after the above code to dump a description of the replacements variable):
# "declare -p replacements" will then print this description of the new array generated...
# ...by the code given above:
declare -a replacements='([73]="02" [75]="01" [84]="03" [99]="04")'
You can then trivially look up any entry in it as a constant-time operation that requires no external commands:
$ echo "${replacements[73]}"
02
...or iterate through the keys and associated values independently:
for key in "${!replacements[#]}"; do
value=${replacements[$key]}
echo "Key $key has value $value"
done
...which will emit:
Key 73 has value 02
Key 75 has value 01
Key 84 has value 03
Key 99 has value 04
Notes/References:
See the bash-hackers wiki on parameter expansion for understanding of the syntax used to slice the elements (${arg:0:2} and ${arg:2}).
See BashFAQ #5 or the BashGuide on arrays for more details on the syntax used above.

Bash array saving

The think is that I want to save something to an array in bash. The point is that I want one name of file for one array. So I don't know how much arrays I will have.
#!/bin/bash
declare -A NAMES
index=0
for a in recursive.o timeout.o print_recursive.o recfun.o
do
NAMES[$index]=$a
index=$((index+1))
echo ${NAMES[ $index ]}
done
When I run script with -x I can see that NAMES[$index], the index is not there represented as number so the whole thing doesn't work.
The error is at lines 7 and 8. Swap them and it will work.
When index have value 0 you set NAMES[0]=recursive.o, then increment index and print NAMES[1] which not set. And same thing for another elements. Because that there is no output.
Your loop should looks like this:
for a in recursive.o timeout.o print_recursive.o recfun.o
do
NAMES[$index]=$a
echo ${NAMES[$index]}
index=$((index+1))
done
The problem is in the following:
declare -A NAMES
This makes an associative array NAMES. Quoting from help declare:
Options which set attributes:
-a to make NAMEs indexed arrays (if supported)
-A to make NAMEs associative arrays (if supported)
You needed to say:
declare -a NAMES
May be you are trying to do this:
#!/bin/bash
declare -a NAMES
for a in recursive.o timeout.o print_recursive.o recfun.o; do
NAMES+=( "$a" )
done
for (( x=0; x<${#NAMES[#]}; x++ )); do
echo "Index:$x has Value:${NAMES[x]}"
done
Output:
Index:0 has Value:recursive.o
Index:1 has Value:timeout.o
Index:2 has Value:print_recursive.o
Index:3 has Value:recfun.o
Accessing the index which is not set is throwing it off.
NAMES[$index]=$a #Setting up an array with index 0
index=$((index+1)) #Incrementing the index to 1
echo ${NAMES[ $index ]} #Accessing value of index 1 which is not yet set

Resources