Parse array based on variable and nth character - arrays

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.

Related

Bash: Take argument 1 of each line in a file and put it into an array

I have a file that contains many lines. Each line consists of first, a 1 word string, and second, a number (argument 1 and argument 2 are separated by a space). I need to create a script that goes through each line, and assigns argument 1 to an array, and argument 2 to another array. I then need to print both arrays in the same order they were in (in the file) using a loop. Then I must bubble sort the strings (arg1) in alphabetical order, but the numbers (arg2) must follow the string in the same order they were in when they were in the file. I then need to print both arguments (now alphabetical, but with original numbers along side) in their new sorted order.
Here is what I assume it should look like, but I can't get working:
for line in ${filename[*]}; do
for (( i=0; i<=${#filename[*]}; i++ )); do
array=( $(array + arg1) )
done
done
The "array + arg1" is just what I can't figure out. I just can't seem to find out how to get the 1st argument of each line and put it into an array. You'd think it would be simple.
There are many ways to do what you need, here is one as an example:
My file list, with 1 word string and a number for each line:
cat mylist.txt
word_b 20
word_h 80
word_c 30
word_e 50
word_d 40
word_f 60
word_g 70
word_a 10
So, you can do something like this:
#!/bin/bash
# Declare arrays
words_array=()
numbers_array=()
# Read line by line from SORTED file list
filename="mylist.txt"
IFS=$'\n'
for line in `cat $filename | sort`; do
# Assign the word in the first position and the number in the second to variables
word=$(echo $line|awk '{print $1}')
number=$(echo $line|awk '{print $2}')
# Append values to each array
words_array+=("$word")
numbers_array+=("$number")
done
# Then use loop and share iterator between arrays
for (( i=0; i<=${#words_array[#]}; i++ )); do
echo "${words_array[$i]} ${numbers_array[$i]}"
done
Output:
./myscript.sh
word_a 10
word_b 20
word_c 30
word_d 40
word_e 50
word_f 60
word_g 70
word_h 80

Convert CSV to Matrix

I'm trying to convert values from the first column in a CSV file and arranging those values from lowest latitude/highest longitude (southwest) to highest latitude/lowest longitude (northeast) into a matrix.
This CSV file has hundreds of lines of data.
I know that I'm going to need to use sort to accomplish this but, I'm not sure if what I have so far is enough.
Sample:
18,49.000,-96.000
30,41.000,-109.000
65,31.000,-80.000
25,47.000,-75.000
45,37.000,-90.000
60,30.000,-100.000
70,30.000,-118.000
...
...
...
Bash Code:
sort -t',' -nr -k2 -k3
Result:
18,49.000,-96.000
25,47.000,-75.000
30,41.000,-109.000
45,37.000,-90.000
65,31.000,-80.000
60,30.000,-100.000
70,30.000,-118.000
Sample Conceptual Graphic:
Expected Matrix Setup:
18
25
30
45
70 60
65
If it is acceptable to use general programming language like Perl, try something like:
perl -e '
while (<>) {
($code, $lat, $long) = split(/,/);
$lats{$lat}++;
$longs{$long}++;
$map{$lat, $long} = $code;
}
for $lat (sort {$b <=> $a} keys %lats) {
for $long (sort {$a <=> $b} keys %longs) {
$str = $map{$lat, $long} || " ";
print $str . " ";
}
print "\n";
}' samplefile
and the result looks like:
18
25
30
45
65
70 60
Note that #65 is not southernmost in your sample data.
Hope this helps.

How to create a map of key:array in shell?

I want to create map in shell. Where each value is an array. So the map is key:array pair. For example it can be like this :
"Key1" : a1 a2 a3 a4
"key2" : b1 b2 b3
"key3" : c1
basically my code looks like this
listService(){
serviceType=$1
servicesList=($(getServices $serviceType))
}
listService serviceTypeA
listService serviceTypeB
listService serviceTypeC
here getServices is a function which returns an array of services based on the argument passed as $serviceType. So every time i call the listService function my serviceList gets overridden by new service list. But I want to keep all the services from different service type in form of a map like this :
"serviceA" : a1 a2 a3 a4
"serviceB" : b1 b2 b3
"serviceC" : c1
After that I want to access each array based on the key. How to achieve this.
Thanks in advance for your help.
Edit : I tried the answer provided by #cdarke . Here is my code now :
#!/bin/bash
declare -A arrayMap
getValues(){
key=$1
case $key in
AAA )
arr=( AA AAA AAAA )
;;
BBB )
arr=( BB BB BBBB )
;;
CCC )
arr=()
;;
esac
echo "${arr[#]}"
}
fillArrayMap(){
param=$1
values=( $(getValues $param) )
printf "\nIn $param\n"
echo -e "\nArray values is: ${values[#]}\n"
printf "\nLength of the array values is : ${#values[#]}\n"
arrayMap["$param"]=$values #THIS IS THE KEY LINE
valuesList=${arrayMap[$param]}
echo -e "\nArray valuesList is: ${valuesList[#]}\n"
printf "\nLength of the array valuesList is : ${#valuesList[#]}\n"
}
fillArrayMap AAA
fillArrayMap BBB
fillArrayMap CCC
Now from output I can see valuesList is getting only the first element of the values array. But I want valuesList to contain all the elements returned by the method getValues. i.e
valuesList= ${arrayMap[$param]}
now valuesList should contain all the elements, instead now it contains only 1 element. How to fix that ?
Note: My goal is to access each individual element like AAA or AA, I don't need it as a whole as a string like AA AAA AAAA
Bash does not support multi-dimensional arrays, but I don't think you need one. You can store a string in the form of a list in an array element, which will give you what you ask for.
# My made-up version of getServices
getServices() {
nm="$1"
last=${nm##*Type}
retn=(${last}1 ${last}2 ${last}3 ${last}4)
echo "${retn[#]}"
}
declare -A serviceList
listService(){
serviceType="$1"
# Here I use the key to make an assignment, which adds to the hash
serviceList["$serviceType"]=$(getServices $serviceType)
}
listService serviceTypeA
listService serviceTypeB
listService serviceTypeC
for key in ${!serviceList[#]}
do
echo "\"$key\": ${serviceList[$key]}"
done
Gives:
"serviceTypeC": C1 C2 C3 C4
"serviceTypeB": B1 B2 B3 B4
"serviceTypeA": A1 A2 A3 A4
EDIT for new question:
alter:
arrayMap["$param"]=$values # THIS IS THE KEY LINE
valuesList=${arrayMap[$param]}
to:
arrayMap["$param"]=${values[#]}
valuesList=( ${arrayMap[$param]} )
When you refer to an array variable by just it's name ($values) you only get the first element.
As cdarke already mentioned, bash arrays are one-dimensional. Over the years, folks have come up with ways to "fake" multi-dimensional arrays.
Two methods I've used are to maintain an array of array descriptions, or an array of pointers to other arrays. I'll answer with the former; the latter should be obvious if you want to explore on your own.
Here's a minimal example of array content getting used to populate variables:
#!/usr/bin/env bash
declare -A a=(
[b]='([0]="one" [1]="two")'
[c]='([0]="three" [1]="four")'
)
declare -p a
for key in ${!a[#]}; do
declare -a $key="${a[$key]}"
declare -p $key
done
Produces:
declare -A a=([b]="([0]=\"one\" [1]=\"two\")" [c]="([0]=\"three\" [1]=\"four\")" )
declare -a b=([0]="one" [1]="two")
declare -a c=([0]="three" [1]="four")
The critical bit here is that you're using declare to refer to the value of $key, since you can't just say $var="value" in bash.
Of course, you don't need to name your variables for the value of $key if you don't want to. Storing values in, say $value, would free you up to use special characters in $key.
An even simpler alternative, if it doesn't offend your sensibilities or restrict your key names too much, is to store the entire output of a declare -p command in the value of the array, and then eval it when you need it. For example:
declare -A a=(
[b]='declare -a b=([0]="one" [1]="two")'
[c]='declare -a c=([0]="three" [1]="four")'
)
for key in ${!a[#]}; do
eval "${a[$key]}"
done
Some people don't like eval. :-) It remains, however in your toolbox.
In your case, it's a little hard to advise because you haven't provided a full MCVE, but here's my contrived example.
#!/usr/bin/env bash
# contrived getServices function, since you didn't provide one
getServices() {
local -a value=()
local last="${1:$((${#1}-1)):1}" # last character of $1
for n in $( seq 1 $(( $RANDOM / 8192 + 1 )) ); do
value+=(${last}${n})
done
declare -p value # output of this function is actual bash code.
}
# populate the array
listService() {
servicesList[$1]=$( getServices $1 )
}
# Initialize this as empty to make `eval` safer
declare -A servicesList=()
# These services seem interesting.
listService serviceA
listService serviceB
listService serviceC
# Note that we're stepping through KEYS here, not values.
for row in "${!servicesList[#]}"; do
printf '"%s": ' "$row"
eval "${servicesList[$row]}" # Someone is bound to complain about this.
for column in "${!value[#]}"; do
# Add whatever $row and $column specific code you like here.
printf '%s ' "${value[$column]}"
done
printf "\n"
done
My output:
$ bash 2dimarrayexample
"serviceC": C1
"serviceB": B1 B2 B3 B4
"serviceA": A1 A2
Of course, your output may differ, since getServices produces random output. :)

Splitting strings in a text file and store to separate variables in shell

I have a text file consisting of strings as shown below. I require to take the last one, split the data and store it into separate variables for later use. I tried using | tr -s " " "\012" which worked but can't find a way to have the data stored in separate variables. Also, I would like to do this in shell. Any suggestions please?
Content of the text file:
324.0 0.4444 79
324.0 0.4445 80
324.0 0.4445 80
324.0 0.4445 80
...
326.0 0.5677 84 ... This is the line of interest
Thanks!
You can use read with sed:
# use sed to get the last line and read it into 3 variables
read -r var1 var2 var3 < <(sed '$!d' fff)
# check your variables
echo "var1=[$var1] var2=[$var2] var3=[$var3]"
Output:
var1=[326.0] var2=[0.5677] var3=[84]
You may do this
read var1 var2 var3 <<<"$(tail -n1 your_file_name)"
echo $var1 $var2 $var3 # testing for the desired result.

Bash- populate an associative array using a loop

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

Resources