bad array subscript in if statement - arrays

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]}" ]]

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

How to create array-variable from function argument input -- Remove Array Blanks function

After seeing this question, I decided to put together a function to remove array empty elements in case it saves someone a few seconds.
Is there any way to return (or export) a dynamically named input array-variable as the output of a function?
Ideally
User calls: removeArrayBlanks "newArrayName" "arrayItem1" "" "arrayItem2"...
The function unsets the old array and creates: ${newArrayName[#]}, which expands to "arrayItem1" "arrayItem2" without any blank items or non-sequential index numbers
Also, does anyone have any optimizations/suggestions to the function? I've included the function below. Thanks!
removeArrayBlanks() {
# Usage: Provide array as input, store output as array.
# Example 1: mapfile -t lastArray < <(removeArrayBlanks "hate" "" "empty" "array" "items")
# Example 2: mapfile -t lastArray < <(removeArrayBlanks "${inputArray[#]}")
[[ $# -lt 2 ]] && echo "Usage: Provide array as an argument." && exit 1 # Ensure input is array
tempArray=( "$#" ) # Rebuild array from inputs
for i in "${!tempArray[#]}"; do
[[ ! -z "${tempArray[$i]}" ]] && finalArray+=( "${tempArray[$i]}" ) # Add non-empty strings
done && printf '%s\n' "${finalArray[#]}" && unset tempArray && unset finalArray
}
Assumptions:
objective is to remove array elements that contain empty strings (aka 'blanks') from current array
function should be able to work for any array (name) passed to the function
OP is running, or can upgrade to/above, bash 4.3+ (needed for nameref support)
NOTE: to 'remove' an array element just unset the array reference (eg, unset array[index#1]
One idea using a nameref in the function and the unset command:
function removeArrayBlanks() {
declare -n localarray="${1}" # define nameref; remains a local array variable
for i in "${!localarray[#]}" # loop through indices
do
[[ -z "${localarray[${i}]}" ]] && unset localarray[${i}] # remove entries that contain empty strings
done
}
In operation:
$ myarray=( a "" 1 "" A )
$ typeset -p myarray
declare -a myarray=([0]="a" [1]="" [2]="1" [3]="" [4]="A")
$ removeArrayBlanks myarray
$ typeset -p myarray
declare -a myarray=([0]="a" [2]="1" [4]="A")
# verify localarray not known by parent
$ typeset -p localarray
-bash: typeset: localarray: not found

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

associative array name substitution and copy bash

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

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