How do you reference a variable within another variable in bash? - arrays

I am trying to do something which I thought would be fairly simple but not having any joy.
If I run the following, it successfully pulls the region (reg) from an array called PORT330 and checks to see it contains the value of $i. For example, it could be checking to see if "Europe London" contains the word "London". Here is what works:
if [[ ${PORT330[reg]} == *"$i"* ]] ; then
echo 302 is in $i ;
fi
However, I actually have a list of Port array's to check so it may be PORT330, PORT550 and so on. I want to be able to substitute the port number with a variable but then call it within a variable. Here is what I am trying to do:
This works:
for portid in ${portids[#]} ; do
for i in ${regions[#]} ; do
if [[ ${PORT330[reg]} == *"$i"* ]] ; then
echo $portid is in $i ;
fi ;
done ;
done
However this doesn't work:
for portid in ${portids[#]} ; do
for i in ${regions[#]} ; do
if [[ ${PORT$portid[reg]} == *"$i"* ]] ; then
echo $portid is in $i ;
fi ;
done ;
done
It throws this error:
-su: ${PORT$portid[reg]}: bad substitution
Any pointers as to where I am going wrong?

In BASH variable expansion there is an option to have indirection via the ${!name} and ${!name[index]} schemes
for portid in ${portids[#]} ; do
for i in ${regions[#]} ; do
arr=PORT$portid
if [[ ${!arr[reg]} == *"$i"* ]] ; then
echo $portid is in $i ;
fi ;
done ;
done
Here is a complete example
PORT330[reg]=a ;
PORT550[reg]=b ;
for portid in 330 550 ; do
for i in a b ;
do arr=PORT$portid ;
if [[ ${!arr[reg]} == *"$i"* ]] ; then
echo $portid is in $i ;
fi ;
done ;
done
Produces
330 is in a
550 is in b
Another example
:~> portids=(330 350 )
:~> echo ${portids[#]}
330 350
:~> PORT350[reg]=London
:~> PORT330[reg]=Berlin
:~> for portid in ${portids[#]} ; do arr=PORT$portid ; echo ${!arr[reg]} ; done
Berlin
London

Related

printing invalid options using arrays

I am storing details on invalid option to a function using arrays, w to store the positional index and warg to store the option name.
declare -a w=()
declare -a warg=()
local k=0 vb=0 ctp="" sty=""
while (( $# > 0 )); do
k=$((k+1))
arg="$1"
case $arg in
("--vlt")
ctp="$vlt" ; r=$k ; shift 1 ;;
("--blu")
blu=$(tput setaf 12)
ctp="$blu" ; r=$k ; shift 1 ;;
("--grn")
grn=$(tput setaf 2)
ctp="$grn" ; r=$k ; shift 1 ;;
("-m"*)
sty="${1#-m}" ; r=$k ; shift 1 ;;
("--")
shift 1 ; break ;;
("-"*)
w+=($k) ; warg+=("$arg")
shift 1 ;;
(*)
break ;;
esac
done
After that, I try to loop through the invalid options but as one can see, the positional elements in iw do not map to ${warg[$iw]}, in a way that I can print the invalid option name. What can I do?
r=4
local iw
for iw in "${w[#]}"; do
if (( iw < r )); then
printf '%s\n' "Invalid option | ${warg[$iw]}"
fi
done
Numerically-indexed arrays don't have to have sequentical indices.
Using an array of color name to number reduces the duplication in the case branches.
declare -A colors=(
[blu]=12
[grn]=2
[ylw]=3
[orn]=166
[pur]=93
[red]=1
[wht]=7
)
declare -a warg=()
for ((i = 1; i <= $#; i++)); do
arg=${!i} # value at position $i
case $arg in
--blu|--grn|--ylw|--orn|--pur|--red|--wht)
ctp=$(tput setaf "${colors[${arg#--}]}") ;;
--vlt) ctp="$vlt" ;;
-m*) sty="${arg#-m}" ;;
--) break ;;
-*) warg[i]=$arg ;;
*) break ;;
esac
done
numArgs=$i
shift $numArgs
# iterate over the _indices_ of the wrong args array
for i in "${!warg[#]}"; do
echo "wrong arg ${warg[i]} at postition $i"
done
That is untested, so there may be bugs
Addressing just the mapping issue between w[] and warg[] ...
The problem seems to be that $k is being incremented for every $arg, but the index for warg[] is only incremented for an 'invalid' $arg.
If the sole purpose of the w[] array is to track the position of 'invalid' args then you can eliminate the w[] array and populate warg[] as a sparse array with the following change to the current code:
# replace:
warg+=("$arg")
# with:
warg[$k]="$arg"
With this change your for loop becomes:
for iw in "${!warg[#]}"
do
....
printf '%s\n' "Invalid option | ${warg[$iw]}"
done

How to swap array element in bash shell linux?

I have a problem regarding about the syntax on how to swap the element value in an array.
array="5 3 2 1 4"
echo "${array[*]}"
changed=1
while [ $changed != 0 ]
do
changed=0
for (( i=0 ; i<=${#array[#]}-1 ; i++ ))
do
if [ ${array[$i]} -gt ${array[$i+1]} ]
then
tmp=${array[$i]}
array[$i]=${array[$i+1]}
array[$i+1]=$tmp
changed=1
fi
done
done
echo "Sorted array: "
echo "${array[*]}"
Edit:
Thanks for answering my question. I have changed the code, and now and it looks something like this.
But unfortunately there is still a problem.
It says:
jdoodle.sh: line 3: $'\r': command not found
jdoodle.sh: line 8: syntax error near unexpected token `$'\r''
jdoodle.sh: line 8: ` for ((i=0;i<=${#array[#]}-1;i++))
This is one implementation of bubble sort:
#!/bin/bash
array=(5 3 2 1 4)
echo "${array[*]}"
size=${#array[#]}
for (( i=0; i<size-1; i++ )); do
for (( j=0; j<size-i-1; j++ )); do
if (( array[j] > array[j+1] )); then
tmp=${array[j]}
array[j]=${array[j+1]}
array[j+1]=$tmp
fi
done
done
echo "Sorted array:"
echo "${array[*]}"
Major problem with your code is that it actually does not use arrays.
Define arrays like array=(value1 value2 value3). It's also better to use [[ ]] for testing instead of [ ]. If we were to change your code just a little to also create a functioning bubble sort algorithm, it could look like this:
#!/bin/bash
array=(5 3 2 1 4)
echo "${array[*]}"
changed=1 j=0
while [[ $changed != 0 ]]
do
((j++))
changed=0
for (( i=0; i<${#array[#]}-j; i++ ))
do
if [[ ${array[i]} -gt ${array[i+1]} ]]
then
tmp=${array[i]}
array[i]=${array[i+1]}
array[i+1]=$tmp
changed=1
fi
done
done
echo "Sorted array:"
echo "${array[*]}"
I do not get the \r messages, even in your test environment; in general, they are a result of DOS/Windows combatability (with a b).
Since this is obviously a tutorial example (why else would someone do bubblesort), some remarks about the code.
array="5 3 2 1 4"
does not create the array that you want. It creates a string. What you are looking for is:
array=(5 3 2 1 4)
The last element of the array is ${#array[#]}-1. Element counting starts at 0. So your for-loop should be:
for (( i=0 ; i<=${#array[#]}-2 ; i++ ))
-2 because you're referencing ${array[$i+1]}, which would otherwise be outside the boundary.

Bash: Iterating through an array to get the value at each index

The main goal of this program is to simulate the drawing of a card as many times as the user chooses and then print out a histogram using '*' to represent the number of hits on each card. However, the problem that I am having is retrieving the elements in each array and printing the stars that correlates with them. This is what I have so far:
timelimit=5
echo -e "How many trials would you like to run? \c"
read -t $timelimit trials
if [ ! -z "$trials" ]
then
echo -e "\nWe will now run $trials trials"
else
trials=10
echo -e "\nWe will now run the default amount of trials: $trials"
fi
count=1
MAXCARD=53
declare -a CARDARRAY
while [ "$count" -le $trials ]
do
card=$RANDOM
let "card %= MAXCARD"
let "CARDARRAY[$card] += 1"
let "count += 1"
done
echo ${CARDARRAY[#]}
for (( i=0; i<${#CARDARRAY[#]}; i++));
do
#declare "temp"="${CARDARRAY[$i]}"
#echo "$temp"
#for (( j=0; j<temp; j++));
#do
#echo "*"
#done
echo "$i"
done
Obviously the last for loop is where I'm having trouble and is currently the latest attempt at printing the stars according to how many hits each card has.
You were pretty close. Here's how I'd paraphrase your script:
#!/bin/bash
timelimit=5
printf %s 'How many trials would you like to run? '
read -t $timelimit trials
if [[ ! -z $trials ]] ; then
printf '\nWe will now run %d trials\n' $trials
else
trials=10
printf '\nWe will now run the default amount of trials: %d\n' $trials
fi
count=1
MAXCARD=53
declare -a CARDARRAY
while (( trials-- )) ; do
(( CARDARRAY[RANDOM % MAXCARD] += 1 ))
done
printf '%s\n' "${CARDARRAY[*]}"
for (( i=0 ; i<MAXCARD ; i++ )) ; do
printf %02d: $i
for (( j=0 ; j<${CARDARRAY[i]:-0} ; j++ )) ; do
printf %s '*'
done
printf '\n' ''
done
You can use set -xv to see what bash is running at each step.

Problems getting the size of an array in bash

I have this code to get all the lines filtered by the expression used the grep:
arrvar=( $(grep -Poh '^[A-Z_]+=.+' input.txt) )
arrlen=${#arrvar[#]}
i=0
while : ; do
split=(${arrvar[i]//=/ })
name="${split[0]}"
value="${split[1]}"
echo "index..: $i"
echo "name...: $name"
echo "value..: $value"
i=$(( i + 1 ))
if [ $i > $arrlen ]; then
break
fi
done
Whit this content in input.txt:
HELLO=111
STACK=222
OVERFLOW=333
The result is the following:
index..: 0
name...: STACK
value..: 222
Why only returns the first item of the array instead the three of the file?
You are testing like this:
if [ $i > $arrlen ]
but you probably mean
if (( i > arrlen ))
[ compares lexicographically while (( compares numerical.

zsh: check if string is in array

E.g.
foo=(a b c)
Now, how can I do an easy check if b is in $foo?
You can use reverse subscripting:
pax$ foo=(a b c)
pax$ if [[ ${foo[(r)b]} == b ]] ; then ; echo yes ; else ; echo no ; fi
yes
pax$ if [[ ${foo[(r)x]} == x ]] ; then ; echo yes ; else ; echo no ; fi
no
You'll find the datails under man zshparam under Subscript Flags (at least in zsh 4.3.10 under Ubuntu 10.10).
Alternatively (thanks to geekosaur for this), you can use:
pax$ if [[ ${foo[(i)b]} -le ${#foo} ]] ; then ; echo yes ; else ; echo no ; fi
You can see what you get out of those two expressions by simply doing:
pax$ echo ${foo[(i)a]} ${#foo}
1 3
pax$ echo ${foo[(i)b]} ${#foo}
2 3
pax$ echo ${foo[(i)c]} ${#foo}
3 3
pax$ echo ${foo[(i)d]} ${#foo}
4 3
(( ${foo[(I)b]} )) \
&& echo "it's in" \
|| echo "it's somewhere else maybe"
Reverse subscripts will always return something if a match is found and will return nothing if a match is not found. We can use this to simplify the answer even more.
$ foo=(a b c)
$ [[ -n "${foo[(r)b]}" ]] && echo 'b was found.' || echo 'b was not found.'
b was found.
$ [[ -n "${foo[(r)d]}" ]] && echo 'd was found.' || echo 'd was not found.'
d was not found.

Resources