associative array name substitution and copy bash - arrays

I have a bash script which checks if the input date($1) falls in a range/ranges of dates. User inputs a date and (a or b, which is $2).
#!/usr/bin/env bash
today=$(date +"%Y%M%d")
declare -A dict=$2_range
a_range=( ["20140602"]="20151222" ["20170201"]="$today" )
b_range=( ["20140602"]="20150130" )
for key in ${!dict[#]}; do
if [[ $1 -le ${dict[$key]} ]] && [[ $1 -ge $key ]]; then
echo $1 falls in the range of $2
fi
done
I do not know how to copy the associative array to the dict variable.
Sample usage
$ ./script.sh 20170707 a
20170707 falls in the range of a

You don't need to copy anything at all; you just need an alias.
#!/usr/bin/env bash
today=$(date +"%Y%M%d")
# you need declare -A **before** data is given.
# previously, these were sparse indexed arrays, not associative arrays at all.
declare -A a_range=( ["20140602"]="20151222" ["20170201"]="$today" )
declare -A b_range=( ["20140602"]="20150130" )
# declare -n makes dict a reference to (not a copy of) your named range.
declare -n dict="$2_range"
for key in "${!dict[#]}"; do
if (( $1 <= ${dict[$key]} )) && (( $1 >= key )); then
echo "$1 falls in the range of $2"
fi
done
declare -n is the bash (4.3+) version of the ksh93 feature nameref; see http://wiki.bash-hackers.org/commands/builtin/declare#nameref

Related

Bash how to substitute string variables for array name [duplicate]

Let's say we declared two associative arrays:
#!/bin/bash
declare -A first
declare -A second
first=([ele]=value [elem]=valuee [element]=valueee)
second=([ele]=foo [elem]=fooo [element]=foooo)
# echo ${$1[$2]}
I want to echo the given hashmap and element from script inputs. For example, if I run sh.sh second elem, the script should echo fooo.
An inelegant but bullet-proof solution would be to white-list $1 with the allowed values:
#!/bin/bash
# ...
[[ $2 ]] || exit 1
unset result
case $1 in
first) [[ ${first["$2"]+X} ]] && result=${first["$2"]} ;;
second) [[ ${second["$2"]+X} ]] && result=${second["$2"]} ;;
*) exit 1 ;;
esac
[[ ${result+X} ]] && printf '%s\n' "$result"
notes:
[[ $2 ]] || exit 1 because bash doesn't allow empty keys
[[ ${var+X} ]] checks that the variable var is defined; with this expansion you can also check that an index or key is defined in an array.
A couple ideas come to mind:
Variable indirection expansion
Per this answer:
arr="$1[$2]" # build array reference from input fields
echo "${!arr}" # indirect reference via the ! character
For the sample call sh.sh second elem this generates:
fooo
Nameref (declare -n) (requires bash 4.3+)
declare -n arr="$1"
echo "${arr[$2]}"
For the sample call sh.sh second elem this generates:
fooo

bad array subscript in if statement

I have a script where I'm creating a couple of a couple of command line arguments to pass in (file and output). The script appears to be working as I can successfully pass files into the script and parse them with the "-f" flag, but I noticed I'm getting the following error (on this line in the code below: if [[ -n ${variables[$argument_label]} ]]):
line 29: variables: bad array subscript
# declaring a couple of associative arrays
declare -A arguments=();
declare -A variables=();
# declaring an index integer
declare -i index=1;
variables["-o"]="output";
variables["--output"]="output";
variables["-f"]="file";
variables["--file"]="file";
# $# here represents all arguments passed in
for i in "$#"
do
arguments[$index]=$i;
prev_index="$(expr $index - 1)";
# this if block does something akin to "where $i contains ="
# "%=*" here strips out everything from the = to the end of the argument leaving only the label
if [[ $i == *"="* ]]
then argument_label=${i%=*}
else argument_label=${arguments[$prev_index]}
fi
# this if block only evaluates to true if the argument label exists in the variables array
if [[ -n ${variables[$argument_label]} ]]
then
# dynamically creating variables names using declare
# "#$argument_label=" here strips out the label leaving only the value
if [[ $i == *"="* ]]
then declare ${variables[$argument_label]}=${i#$argument_label=}
else declare ${variables[$argument_label]}=${arguments[$index]}
fi
fi
index=index+1;
done;
Any ideas or insight is greatly appreciated.
Maybe try adding quotes?
if [[ -n "${variables[$argument_label]}" ]]

Creating an array from file and name it based on a line in bash

I have text file like this:
src_dir=source1
src_dir=source2
dst_dir=dest1
whatever_thing=thing1
whatever_thing=thing2
I want a script that will create arrays with names from the left part of a line and fill it with elements from the right part of a line. So basically it should do:
src_dir=(source1 source2)
dst_dir=(dest1)
whatever_thing=(thing1 thing2)
I've tried so far:
while read -r -a line
do
IFS='= ' read -r -a array <<< "$line"
"${array[0]}"+="${array[1]}"
done < file.txt
If your bash version is 4.3 or newer, declare has an -n option
to define a rerefence to the variable name which works as a reference in C++.
Then please try the following:
while IFS== read -r key val; do
declare -n name=$key
name+=("$val")
done < file.txt
# test
echo "${src_dir[#]}"
echo "${dst_dir[#]}"
echo "${whatever_thing[#]}"
try this:
#!/bin/bash
mapfile -t arr < YourDataFile.txt
declare -A dict
for line in "${arr[#]}"; do
key="${line%%=*}"
value="${line#*=}"
[ ${dict["$key"]+X} ] && dict["$key"]+=" $value" || dict["$key"]="$value"
done
for key in "${!dict[#]}"; do
printf "%s=(%s)\n" "$key" "${dict["$key"]}"
done
explanation
# read file into array
mapfile -t arr < YourDataFile.txt
# declare associative array
declare -A dict
# loop over the data array
for line in "${arr[#]}"; do
# extract key
key="${line%%=*}"
# extract value
value="${line#*=}"
# write into associative array
# - if key exists ==> append value
# - else initialize entry
[ ${dict["$key"]+X} ] && dict["$key"]+=" $value" || dict["$key"]="$value"
done
# loop over associative array
for key in "${!dict[#]}"; do
# print key=value pairs
printf "%s=(%s)\n" "$key" "${dict["$key"]}"
done
output
dst_dir=(dest1)
src_dir=(source1 source2)
whatever_thing=(thing1 thing2)

Bash; creating arrays which names are computed from other variables [duplicate]

This question already has an answer here:
Assign to a bash array variable indirectly, by dynamically constructed variable name
(1 answer)
Closed 5 years ago.
My goal is to create a list of partitions for each block device listed in /sys/block;
#!/bin/bash
block_devices_list=($(ls /sys/block))
partition_list=($(cat /proc/partitions | awk '{print $4}'))
unset partition_list[0]
for block_device in ${block_devices_list[#]}; do
for partition in ${partition_list[#]}; do
partitions+=($(echo $partition | grep $block_device))
done
# Right here?
unset partitions
done
Every time the outside 'for loop' completes it's cycle it ends up with an array of partitions for a particular block device. At that point I would like to transfer that data to a separate array, dynamically named after the device it belongs to (like 'partitions_sda' for example).
I have read a few questions/answers about 'dynamic' variable names, 'associative' arrays and whatnot but don't seem to be able to figure this out. Any help much appreciated.
As a best-practices example (for bash 4.3 or newer):
#!/bin/bash
for blockdev in /sys/block/*; do
devname=${blockdev##*/} # remove leading path elements
devname=${devname//-/_} # rename dashes in device name to underscores
declare -a "partitions_${devname}=()" # define an empty array
declare -n _current_partitions="partitions_$devname" # define a nameref
for part in "$blockdev"/*/dev; do # iterate over partitions
[[ -e $part ]] || continue # skip if no matches
part=${part%/dev} # strip trailing /dev
_current_partitions+=( "${part##*/}" ) # add match via nameref
done
unset -n _current_partitions # clear the nameref
declare -p "partitions_$devname" # print our resulting array
done
For my local test VM, this emits:
declare -a partitions_dm_0=()
declare -a partitions_dm_1=()
declare -a partitions_sda=([0]="sda1" [1]="sda2")
declare -a partitions_sdb=()
declare -a partitions_sr0=()
...which is correct, as sda is the only partitioned device.
The basic mechanism here is the nameref: declare -n name1=name2 will allow one to refer to the variable name2 under the name name1, including updates or assignments, until unset -n name1 is performed.
Not sure I understood what you are trying to do but this is an example that you can, maybe, start with:
#!/bin/bash
list1=(a b c)
list2=(j k l)
for x in ${list1[#]}; do
for y in ${list2[#]}; do
tmplist+=(${x}${y})
done
cmd="declare -a list_${x}=(${tmplist[#]})"
eval $cmd
unset tmplist
done
echo "list_a: ${list_a[#]}"
echo "list_b: ${list_b[#]}"
echo "list_c: ${list_c[#]}"
It shows how to create arrays which names are computed from other variables, how to unset a temporary array...
I would create two types of arrays. One for containing disk names, then different arrays for each disk for storing partition specific information.
#!/usr/bin/env bash
while read -r l; do
t="${l#* }"
if [[ $t == "disk" ]]; then
# disks list contains all the disk names
disks+="${l% *} "
elif [[ $t == "part" ]]; then
# for each disk 'XXX', a separate partitions array 'parts_XXX' is created
[[ $l =~ [^a-z0-9]*([a-z]*)([0-9]*)\ ]] && d="${BASH_REMATCH[1]}" && p="${BASH_REMATCH[2]}"
eval parts_$d+=\"my-value=\$d\$p \" # append to partitians array
else
echo "unknown type $t in $l"
exit 1
fi
done <<< $(lsblk -n --output=NAME,TYPE | tr -s ' ')
# arrays are created. now iterate them
for i in ${disks[#]}; do
echo "iterating partitions of disk: $i"
# following is the name of the current disks partitions array
var=parts_$i[#]
# iterate partitians array of the current disk
for j in ${!var}; do
echo ">> $j"
done
done

Bash array with each character of that array element as a character of a string

I am trying to loop though an array in bash. The array is "AARON" currently that array fills index 0 with AARON. I want to be behave as such
ara[0] = A
ara[1] = A
ara[2] = R
ara[3] = O
ara[4] = N
My script currently looks like this.
#!/bin/bash
declare count
declare -a ara=(AARON)
for name in {A..Z}
do
count=$((count++))
for char in ${ara[*]}
do
if [[ ${ara[char]} -eq $name ]]
echo ${ara[char]} # for debugging purposes.
then
sum=$((sum + count))
fi
done
done
echo $sum #for debugging purposes.
Normally I would achieve this by setting a custom IFS but I don't know how I would do that for each character of string.
You want = not -eq for string comparison.
Your echo ${ara[char]} line is part of your if test not part of the if body.
Your count=$((count++)) assignment doesn't work correctly (it always sets count to zero) you want ((count++)) or count=$((++count)) instead.
You can use ${strvar:pos:count} to get parts of a string.
So something like this:
#!/bin/bash
declare count
declare -a ara=(AARON)
for name in {A..Z}
do
((count++))
for ((i=0; i<=${#ara[0]}; i++))
do
echo ${ara[0]:i:1} # for debugging purposes.
if [[ ${ara[0]:i:1} = $name ]]
then
sum=$((sum + count))
fi
done
done
echo $sum # for debugging purposes.
I would do something like this:
declare count
declare -a ara=(A A R O N)
for name in {A..Z}
do
for character in ${ara[*]}
do
if [[ ${character} == ${name} ]]
then
echo ${character} '=' ${name} # for debugging purposes.
let 'sum=sum+1'
fi
done
done
echo $sum #for debugging purposes.
You could add spaces into every letter to convert AARON in a bash array. Moreover, you could use "let" to inrease the counter.

Resources