Bash -how to check if variable value is in an array - arrays

I have an array that has 6 values, i then have three more arrays with anywhere between 4 and 1 value. i want to loop through array 1 and check if the value from array one appears in array2, array 3 and array 4. Currently i have the below but it only appears to check my value from array1 against the first value in arrays 2,3 and 4. i have omitted array 3 and 4 but they would have the same for loop as array2 and be inside the loop for array1.
array1=("value1" "value2" "value3" "value4" "value5" "value6")
for i in "${array1}"; do
array2= ("value1" "value3" "value4" "value5")
for f in "${array2}; do
if [[ ${i} == ${f} ]]; then
echo "${i} in array1 matches ${f} in array2"
else
echo "${i} in array1 does not match any value in array2"
fi
done
done

I think that the best thing to do is to make a function
in_array () {
search=$1
shift # remove first argument from list of args $#
for val; do # equivalent to `for val in "$#"`
if [[ $search = $val ]]; then
return # returns exit code of the successful [[ test ]], 0
fi
done
return 1
}
This returns 0 if the value is found, else 1, allowing you to use it like:
array1=("value1" "value2" "value3" "value4" "value5" "value6")
array2=("value1" "value3" "value4" "value5")
for i in "${array1[#]}"; do
if in_array "$i" "${array2[#]}"; then
echo "$i in array1 is in array2"
fi
done
Note that to loop through all the values of an array, the correct expansion is "${array[#]}" (with double quotes and [#]).

This is possible in a single loop:
#!/usr/bin/env bash
array1=("value1" "value2" "value3" "value4" "value5" "value6")
array2=("value1" "value3" "value4" "value5")
while read -r -d '' -n 8 count && IFS= read -r value; do
if [ "$count" -gt 1 ]; then
# value is seen more than once, so it is in both arrays
echo "${value} in array1 matches ${value} in array2"
else
# value is only seen once
if printf $'%s\n' "${array1[#]}" | grep --quiet "$value"; then
# value is from array1
echo "${value} in array1 does not match any value in array2"
fi
fi
done< <(
# Combine both arrays
# Sort and count number of times each value appears
# then feed the while loop
printf $'%s\n' "${array1[#]}" "${array2[#]}" | sort | uniq --count
)

Related

how to using Dynamic Value in bash script [duplicate]

I'm wondering how to declare a 2D array in bash and then initialize to 0.
In C it looks like this:
int a[4][5] = {0};
And how do I assign a value to an element? As in C:
a[2][3] = 3;
You can simulate them for example with hashes, but need care about the leading zeroes and many other things. The next demonstration works, but it is far from optimal solution.
#!/bin/bash
declare -A matrix
num_rows=4
num_columns=5
for ((i=1;i<=num_rows;i++)) do
for ((j=1;j<=num_columns;j++)) do
matrix[$i,$j]=$RANDOM
done
done
f1="%$((${#num_rows}+1))s"
f2=" %9s"
printf "$f1" ''
for ((i=1;i<=num_rows;i++)) do
printf "$f2" $i
done
echo
for ((j=1;j<=num_columns;j++)) do
printf "$f1" $j
for ((i=1;i<=num_rows;i++)) do
printf "$f2" ${matrix[$i,$j]}
done
echo
done
the above example creates a 4x5 matrix with random numbers and print it transposed, with the example result
1 2 3 4
1 18006 31193 16110 23297
2 26229 19869 1140 19837
3 8192 2181 25512 2318
4 3269 25516 18701 7977
5 31775 17358 4468 30345
The principle is: Creating one associative array where the index is an string like 3,4. The benefits:
it's possible to use for any-dimension arrays ;) like: 30,40,2 for 3 dimensional.
the syntax is close to "C" like arrays ${matrix[2,3]}
Bash doesn't have multi-dimensional array. But you can simulate a somewhat similar effect with associative arrays. The following is an example of associative array pretending to be used as multi-dimensional array:
declare -A arr
arr[0,0]=0
arr[0,1]=1
arr[1,0]=2
arr[1,1]=3
echo "${arr[0,0]} ${arr[0,1]}" # will print 0 1
If you don't declare the array as associative (with -A), the above won't work. For example, if you omit the declare -A arr line, the echo will print 2 3 instead of 0 1, because 0,0, 1,0 and such will be taken as arithmetic expression and evaluated to 0 (the value to the right of the comma operator).
Bash does not support multidimensional arrays.
You can simulate it though by using indirect expansion:
#!/bin/bash
declare -a a0=(1 2 3 4)
declare -a a1=(5 6 7 8)
var="a1[1]"
echo ${!var} # outputs 6
Assignments are also possible with this method:
let $var=55
echo ${a1[1]} # outputs 55
Edit 1: To read such an array from a file, with each row on a line, and values delimited by space, use this:
idx=0
while read -a a$idx; do
let idx++;
done </tmp/some_file
Edit 2: To declare and initialize a0..a3[0..4] to 0, you could run:
for i in {0..3}; do
eval "declare -a a$i=( $(for j in {0..4}; do echo 0; done) )"
done
Another approach is you can represent each row as a string, i.e. mapping the 2D array into an 1D array. Then, all you need to do is unpack and repack the row's string whenever you make an edit:
# Init a 4x5 matrix
a=("00 01 02 03 04" "10 11 12 13 14" "20 21 22 23 24" "30 31 32 33 34")
aset() {
row=$1
col=$2
value=$3
IFS=' ' read -r -a rowdata <<< "${a[$row]}"
rowdata[$col]=$value
a[$row]="${rowdata[#]}"
}
aget() {
row=$1
col=$2
IFS=' ' read -r -a rowdata <<< "${a[$row]}"
echo ${rowdata[$col]}
}
aprint() {
for rowdata in "${a[#]}"; do
echo $rowdata
done
}
echo "Matrix before change"
aprint
# Outputs: a[2][3] == 23
echo "a[2][3] == $( aget 2 3 )"
echo "a[2][3] = 9999"
aset 2 3 9999
# Show result
echo "Matrix after change"
aprint
Outputs:
Matrix before change
00 01 02 03 04
10 11 12 13 14
20 21 22 23 24
30 31 32 33 34
a[2][3] == 23
a[2][3] = 9999
Matrix after change
00 01 02 03 04
10 11 12 13 14
20 21 22 9999 24
30 31 32 33 34
You can also approach this in a much less smarter fashion
q=()
q+=( 1-2 )
q+=( a-b )
for set in ${q[#]};
do
echo ${set%%-*}
echo ${set##*-}
done
of course a 22 line solution or indirection is probably the better way to go and why not sprinkle eval every where to .
2D array can be achieved in bash by declaring 1D array and then elements can be accessed using (r * col_size) + c). Below logic delcares 1D array (str_2d_arr) and prints as 2D array.
col_size=3
str_2d_arr=()
str_2d_arr+=('abc' '200' 'xyz')
str_2d_arr+=('def' '300' 'ccc')
str_2d_arr+=('aaa' '400' 'ddd')
echo "Print 2D array"
col_count=0
for elem in ${str_2d_arr[#]}; do
if [ ${col_count} -eq ${col_size} ]; then
echo ""
col_count=0
fi
echo -e "$elem \c"
((col_count++))
done
echo ""
Output is
Print 2D array
abc 200 xyz
def 300 ccc
aaa 400 ddd
Below logic is very useful to get each row from the above declared 1D array str_2d_arr.
# Get nth row and update to 2nd arg
get_row_n()
{
row=$1
local -n a=$2
start_idx=$((row * col_size))
for ((i = 0; i < ${col_size}; i++)); do
idx=$((start_idx + i))
a+=(${str_2d_arr[${idx}]})
done
}
arr=()
get_row_n 0 arr
echo "Row 0"
for e in ${arr[#]}; do
echo -e "$e \c"
done
echo ""
Output is
Row 0
abc 200 xyz
A way to simulate arrays in bash (it can be adapted for any number of dimensions of an array):
#!/bin/bash
## The following functions implement vectors (arrays) operations in bash:
## Definition of a vector <v>:
## v_0 - variable that stores the number of elements of the vector
## v_1..v_n, where n=v_0 - variables that store the values of the vector elements
VectorAddElementNext () {
# Vector Add Element Next
# Adds the string contained in variable $2 in the next element position (vector length + 1) in vector $1
local elem_value
local vector_length
local elem_name
eval elem_value=\"\$$2\"
eval vector_length=\$$1\_0
if [ -z "$vector_length" ]; then
vector_length=$((0))
fi
vector_length=$(( vector_length + 1 ))
elem_name=$1_$vector_length
eval $elem_name=\"\$elem_value\"
eval $1_0=$vector_length
}
VectorAddElementDVNext () {
# Vector Add Element Direct Value Next
# Adds the string $2 in the next element position (vector length + 1) in vector $1
local elem_value
local vector_length
local elem_name
eval elem_value="$2"
eval vector_length=\$$1\_0
if [ -z "$vector_length" ]; then
vector_length=$((0))
fi
vector_length=$(( vector_length + 1 ))
elem_name=$1_$vector_length
eval $elem_name=\"\$elem_value\"
eval $1_0=$vector_length
}
VectorAddElement () {
# Vector Add Element
# Adds the string contained in the variable $3 in the position contained in $2 (variable or direct value) in the vector $1
local elem_value
local elem_position
local vector_length
local elem_name
eval elem_value=\"\$$3\"
elem_position=$(($2))
eval vector_length=\$$1\_0
if [ -z "$vector_length" ]; then
vector_length=$((0))
fi
if [ $elem_position -ge $vector_length ]; then
vector_length=$elem_position
fi
elem_name=$1_$elem_position
eval $elem_name=\"\$elem_value\"
if [ ! $elem_position -eq 0 ]; then
eval $1_0=$vector_length
fi
}
VectorAddElementDV () {
# Vector Add Element
# Adds the string $3 in the position $2 (variable or direct value) in the vector $1
local elem_value
local elem_position
local vector_length
local elem_name
eval elem_value="$3"
elem_position=$(($2))
eval vector_length=\$$1\_0
if [ -z "$vector_length" ]; then
vector_length=$((0))
fi
if [ $elem_position -ge $vector_length ]; then
vector_length=$elem_position
fi
elem_name=$1_$elem_position
eval $elem_name=\"\$elem_value\"
if [ ! $elem_position -eq 0 ]; then
eval $1_0=$vector_length
fi
}
VectorPrint () {
# Vector Print
# Prints all the elements names and values of the vector $1 on sepparate lines
local vector_length
vector_length=$(($1_0))
if [ "$vector_length" = "0" ]; then
echo "Vector \"$1\" is empty!"
else
echo "Vector \"$1\":"
for ((i=1; i<=$vector_length; i++)); do
eval echo \"[$i]: \\\"\$$1\_$i\\\"\"
###OR: eval printf \'\%s\\\n\' \"[\$i]: \\\"\$$1\_$i\\\"\"
done
fi
}
VectorDestroy () {
# Vector Destroy
# Empties all the elements values of the vector $1
local vector_length
vector_length=$(($1_0))
if [ ! "$vector_length" = "0" ]; then
for ((i=1; i<=$vector_length; i++)); do
unset $1_$i
done
unset $1_0
fi
}
##################
### MAIN START ###
##################
## Setting vector 'params' with all the parameters received by the script:
for ((i=1; i<=$#; i++)); do
eval param="\${$i}"
VectorAddElementNext params param
done
# Printing the vector 'params':
VectorPrint params
read temp
## Setting vector 'params2' with the elements of the vector 'params' in reversed order:
if [ -n "$params_0" ]; then
for ((i=1; i<=$params_0; i++)); do
count=$((params_0-i+1))
VectorAddElement params2 count params_$i
done
fi
# Printing the vector 'params2':
VectorPrint params2
read temp
## Getting the values of 'params2'`s elements and printing them:
if [ -n "$params2_0" ]; then
echo "Printing the elements of the vector 'params2':"
for ((i=1; i<=$params2_0; i++)); do
eval current_elem_value=\"\$params2\_$i\"
echo "params2_$i=\"$current_elem_value\""
done
else
echo "Vector 'params2' is empty!"
fi
read temp
## Creating a two dimensional array ('a'):
for ((i=1; i<=10; i++)); do
VectorAddElement a 0 i
for ((j=1; j<=8; j++)); do
value=$(( 8 * ( i - 1 ) + j ))
VectorAddElementDV a_$i $j $value
done
done
## Manually printing the two dimensional array ('a'):
echo "Printing the two-dimensional array 'a':"
if [ -n "$a_0" ]; then
for ((i=1; i<=$a_0; i++)); do
eval current_vector_lenght=\$a\_$i\_0
if [ -n "$current_vector_lenght" ]; then
for ((j=1; j<=$current_vector_lenght; j++)); do
eval value=\"\$a\_$i\_$j\"
printf "$value "
done
fi
printf "\n"
done
fi
################
### MAIN END ###
################
If each row of the matrix is the same size, then you can simply use a linear array and multiplication.
That is,
a=()
for (( i=0; i<4; ++i )); do
for (( j=0; j<5; ++j )); do
a[i*5+j]=0
done
done
Then your a[2][3] = 3 becomes
a[2*5+3] = 3
This approach might be worth turning into a set of functions, but since you can't pass arrays to or return arrays from functions, you would have to use pass-by-name and sometimes eval. So I tend to file multidimensional arrays under "things bash is simply Not Meant To Do".
One can simply define two functions to write ($4 is the assigned value) and read a matrix with arbitrary name ($1) and indexes ($2 and $3) exploiting eval and indirect referencing.
#!/bin/bash
matrix_write () {
eval $1"_"$2"_"$3=$4
# aux=$1"_"$2"_"$3 # Alternative way
# let $aux=$4 # ---
}
matrix_read () {
aux=$1"_"$2"_"$3
echo ${!aux}
}
for ((i=1;i<10;i=i+1)); do
for ((j=1;j<10;j=j+1)); do
matrix_write a $i $j $[$i*10+$j]
done
done
for ((i=1;i<10;i=i+1)); do
for ((j=1;j<10;j=j+1)); do
echo "a_"$i"_"$j"="$(matrix_read a $i $j)
done
done
Mark Reed suggested a very good solution for 2D arrays (matrix)! They always can be converted in a 1D array (vector). Although Bash doesn't have a native support for 2D arrays, it's not that hard to create a simple ADT around the mentioned principle.
Here is a barebone example with no argument checks, etc, just to keep the solution clear: the array's size is set as two first elements in the instance (documentation for the Bash module that implements a matrix ADT, https://github.com/vorakl/bash-libs/blob/master/src.docs/content/pages/matrix.rst )
#!/bin/bash
matrix_init() {
# matrix_init instance x y data ...
declare -n self=$1
declare -i width=$2 height=$3
shift 3;
self=(${width} ${height} "$#")
}
matrix_get() {
# matrix_get instance x y
declare -n self=$1
declare -i x=$2 y=$3
declare -i width=${self[0]} height=${self[1]}
echo "${self[2+y*width+x]}"
}
matrix_set() {
# matrix_set instance x y data
declare -n self=$1
declare -i x=$2 y=$3
declare data="$4"
declare -i width=${self[0]} height=${self[1]}
self[2+y*width+x]="${data}"
}
matrix_destroy() {
# matrix_destroy instance
declare -n self=$1
unset self
}
# my_matrix[3][2]=( (one, two, three), ("1 1" "2 2" "3 3") )
matrix_init my_matrix \
3 2 \
one two three \
"1 1" "2 2" "3 3"
# print my_matrix[2][0]
matrix_get my_matrix 2 0
# print my_matrix[1][1]
matrix_get my_matrix 1 1
# my_matrix[1][1]="4 4 4"
matrix_set my_matrix 1 1 "4 4 4"
# print my_matrix[1][1]
matrix_get my_matrix 1 1
# remove my_matrix
matrix_destroy my_matrix
For simulating a 2-dimensional array, I first load the first n-elements (the elements of the first column)
local pano_array=()
i=0
for line in $(grep "filename" "$file")
do
url=$(extract_url_from_xml $line)
pano_array[i]="$url"
i=$((i+1))
done
To add the second column, I define the size of the first column and calculate the values in an offset variable
array_len="${#pano_array[#]}"
i=0
while [[ $i -lt $array_len ]]
do
url="${pano_array[$i]}"
offset=$(($array_len+i))
found_file=$(get_file $url)
pano_array[$offset]=$found_file
i=$((i+1))
done
The below code will definitely work provided if you are working on a Mac you have bash version 4. Not only can you declare 0 but this is more of a universal approach to dynamically accepting values.
2D Array
declare -A arr
echo "Enter the row"
read r
echo "Enter the column"
read c
i=0
j=0
echo "Enter the elements"
while [ $i -lt $r ]
do
j=0
while [ $j -lt $c ]
do
echo $i $j
read m
arr[${i},${j}]=$m
j=`expr $j + 1`
done
i=`expr $i + 1`
done
i=0
j=0
while [ $i -lt $r ]
do
j=0
while [ $j -lt $c ]
do
echo -n ${arr[${i},${j}]} " "
j=`expr $j + 1`
done
echo ""
i=`expr $i + 1`
done

Comparing 2 Arrays with sum values

I have an Array filled with sum values of the numbers 1 to 10 and in a for loop i want to check the array for duplicates but these values are e.g. 00034 1 which are 2 numbers so the error message i get is "too many arguments" how can i change it?
Code:
while [ $k -le 10 ]
do
#the line below is the problem i is the value of another
#loop the list is in
if [ ${sumList[i]} -e ${sumList[k]} ]
then
if [$k != $i]
then
echo "collision k: $k mit i: $i"
fi
fi
k=$(($k+1))
done
Quote your variables. And use = instead of -eq to compare strings rather than numbers.
You also need $ before the i and k variables, and you need spaces around [ and ] in your second if.
if [ "${sumList[$i]}" = "${sumList[$k]}" ] && [ $i -ne $k ]
then
echo "Collision k: $k mit i: $i"
fi

How to Construct an Array of Arrays in Bash [duplicate]

I'm wondering how to declare a 2D array in bash and then initialize to 0.
In C it looks like this:
int a[4][5] = {0};
And how do I assign a value to an element? As in C:
a[2][3] = 3;
You can simulate them for example with hashes, but need care about the leading zeroes and many other things. The next demonstration works, but it is far from optimal solution.
#!/bin/bash
declare -A matrix
num_rows=4
num_columns=5
for ((i=1;i<=num_rows;i++)) do
for ((j=1;j<=num_columns;j++)) do
matrix[$i,$j]=$RANDOM
done
done
f1="%$((${#num_rows}+1))s"
f2=" %9s"
printf "$f1" ''
for ((i=1;i<=num_rows;i++)) do
printf "$f2" $i
done
echo
for ((j=1;j<=num_columns;j++)) do
printf "$f1" $j
for ((i=1;i<=num_rows;i++)) do
printf "$f2" ${matrix[$i,$j]}
done
echo
done
the above example creates a 4x5 matrix with random numbers and print it transposed, with the example result
1 2 3 4
1 18006 31193 16110 23297
2 26229 19869 1140 19837
3 8192 2181 25512 2318
4 3269 25516 18701 7977
5 31775 17358 4468 30345
The principle is: Creating one associative array where the index is an string like 3,4. The benefits:
it's possible to use for any-dimension arrays ;) like: 30,40,2 for 3 dimensional.
the syntax is close to "C" like arrays ${matrix[2,3]}
Bash doesn't have multi-dimensional array. But you can simulate a somewhat similar effect with associative arrays. The following is an example of associative array pretending to be used as multi-dimensional array:
declare -A arr
arr[0,0]=0
arr[0,1]=1
arr[1,0]=2
arr[1,1]=3
echo "${arr[0,0]} ${arr[0,1]}" # will print 0 1
If you don't declare the array as associative (with -A), the above won't work. For example, if you omit the declare -A arr line, the echo will print 2 3 instead of 0 1, because 0,0, 1,0 and such will be taken as arithmetic expression and evaluated to 0 (the value to the right of the comma operator).
Bash does not support multidimensional arrays.
You can simulate it though by using indirect expansion:
#!/bin/bash
declare -a a0=(1 2 3 4)
declare -a a1=(5 6 7 8)
var="a1[1]"
echo ${!var} # outputs 6
Assignments are also possible with this method:
let $var=55
echo ${a1[1]} # outputs 55
Edit 1: To read such an array from a file, with each row on a line, and values delimited by space, use this:
idx=0
while read -a a$idx; do
let idx++;
done </tmp/some_file
Edit 2: To declare and initialize a0..a3[0..4] to 0, you could run:
for i in {0..3}; do
eval "declare -a a$i=( $(for j in {0..4}; do echo 0; done) )"
done
Another approach is you can represent each row as a string, i.e. mapping the 2D array into an 1D array. Then, all you need to do is unpack and repack the row's string whenever you make an edit:
# Init a 4x5 matrix
a=("00 01 02 03 04" "10 11 12 13 14" "20 21 22 23 24" "30 31 32 33 34")
aset() {
row=$1
col=$2
value=$3
IFS=' ' read -r -a rowdata <<< "${a[$row]}"
rowdata[$col]=$value
a[$row]="${rowdata[#]}"
}
aget() {
row=$1
col=$2
IFS=' ' read -r -a rowdata <<< "${a[$row]}"
echo ${rowdata[$col]}
}
aprint() {
for rowdata in "${a[#]}"; do
echo $rowdata
done
}
echo "Matrix before change"
aprint
# Outputs: a[2][3] == 23
echo "a[2][3] == $( aget 2 3 )"
echo "a[2][3] = 9999"
aset 2 3 9999
# Show result
echo "Matrix after change"
aprint
Outputs:
Matrix before change
00 01 02 03 04
10 11 12 13 14
20 21 22 23 24
30 31 32 33 34
a[2][3] == 23
a[2][3] = 9999
Matrix after change
00 01 02 03 04
10 11 12 13 14
20 21 22 9999 24
30 31 32 33 34
You can also approach this in a much less smarter fashion
q=()
q+=( 1-2 )
q+=( a-b )
for set in ${q[#]};
do
echo ${set%%-*}
echo ${set##*-}
done
of course a 22 line solution or indirection is probably the better way to go and why not sprinkle eval every where to .
2D array can be achieved in bash by declaring 1D array and then elements can be accessed using (r * col_size) + c). Below logic delcares 1D array (str_2d_arr) and prints as 2D array.
col_size=3
str_2d_arr=()
str_2d_arr+=('abc' '200' 'xyz')
str_2d_arr+=('def' '300' 'ccc')
str_2d_arr+=('aaa' '400' 'ddd')
echo "Print 2D array"
col_count=0
for elem in ${str_2d_arr[#]}; do
if [ ${col_count} -eq ${col_size} ]; then
echo ""
col_count=0
fi
echo -e "$elem \c"
((col_count++))
done
echo ""
Output is
Print 2D array
abc 200 xyz
def 300 ccc
aaa 400 ddd
Below logic is very useful to get each row from the above declared 1D array str_2d_arr.
# Get nth row and update to 2nd arg
get_row_n()
{
row=$1
local -n a=$2
start_idx=$((row * col_size))
for ((i = 0; i < ${col_size}; i++)); do
idx=$((start_idx + i))
a+=(${str_2d_arr[${idx}]})
done
}
arr=()
get_row_n 0 arr
echo "Row 0"
for e in ${arr[#]}; do
echo -e "$e \c"
done
echo ""
Output is
Row 0
abc 200 xyz
A way to simulate arrays in bash (it can be adapted for any number of dimensions of an array):
#!/bin/bash
## The following functions implement vectors (arrays) operations in bash:
## Definition of a vector <v>:
## v_0 - variable that stores the number of elements of the vector
## v_1..v_n, where n=v_0 - variables that store the values of the vector elements
VectorAddElementNext () {
# Vector Add Element Next
# Adds the string contained in variable $2 in the next element position (vector length + 1) in vector $1
local elem_value
local vector_length
local elem_name
eval elem_value=\"\$$2\"
eval vector_length=\$$1\_0
if [ -z "$vector_length" ]; then
vector_length=$((0))
fi
vector_length=$(( vector_length + 1 ))
elem_name=$1_$vector_length
eval $elem_name=\"\$elem_value\"
eval $1_0=$vector_length
}
VectorAddElementDVNext () {
# Vector Add Element Direct Value Next
# Adds the string $2 in the next element position (vector length + 1) in vector $1
local elem_value
local vector_length
local elem_name
eval elem_value="$2"
eval vector_length=\$$1\_0
if [ -z "$vector_length" ]; then
vector_length=$((0))
fi
vector_length=$(( vector_length + 1 ))
elem_name=$1_$vector_length
eval $elem_name=\"\$elem_value\"
eval $1_0=$vector_length
}
VectorAddElement () {
# Vector Add Element
# Adds the string contained in the variable $3 in the position contained in $2 (variable or direct value) in the vector $1
local elem_value
local elem_position
local vector_length
local elem_name
eval elem_value=\"\$$3\"
elem_position=$(($2))
eval vector_length=\$$1\_0
if [ -z "$vector_length" ]; then
vector_length=$((0))
fi
if [ $elem_position -ge $vector_length ]; then
vector_length=$elem_position
fi
elem_name=$1_$elem_position
eval $elem_name=\"\$elem_value\"
if [ ! $elem_position -eq 0 ]; then
eval $1_0=$vector_length
fi
}
VectorAddElementDV () {
# Vector Add Element
# Adds the string $3 in the position $2 (variable or direct value) in the vector $1
local elem_value
local elem_position
local vector_length
local elem_name
eval elem_value="$3"
elem_position=$(($2))
eval vector_length=\$$1\_0
if [ -z "$vector_length" ]; then
vector_length=$((0))
fi
if [ $elem_position -ge $vector_length ]; then
vector_length=$elem_position
fi
elem_name=$1_$elem_position
eval $elem_name=\"\$elem_value\"
if [ ! $elem_position -eq 0 ]; then
eval $1_0=$vector_length
fi
}
VectorPrint () {
# Vector Print
# Prints all the elements names and values of the vector $1 on sepparate lines
local vector_length
vector_length=$(($1_0))
if [ "$vector_length" = "0" ]; then
echo "Vector \"$1\" is empty!"
else
echo "Vector \"$1\":"
for ((i=1; i<=$vector_length; i++)); do
eval echo \"[$i]: \\\"\$$1\_$i\\\"\"
###OR: eval printf \'\%s\\\n\' \"[\$i]: \\\"\$$1\_$i\\\"\"
done
fi
}
VectorDestroy () {
# Vector Destroy
# Empties all the elements values of the vector $1
local vector_length
vector_length=$(($1_0))
if [ ! "$vector_length" = "0" ]; then
for ((i=1; i<=$vector_length; i++)); do
unset $1_$i
done
unset $1_0
fi
}
##################
### MAIN START ###
##################
## Setting vector 'params' with all the parameters received by the script:
for ((i=1; i<=$#; i++)); do
eval param="\${$i}"
VectorAddElementNext params param
done
# Printing the vector 'params':
VectorPrint params
read temp
## Setting vector 'params2' with the elements of the vector 'params' in reversed order:
if [ -n "$params_0" ]; then
for ((i=1; i<=$params_0; i++)); do
count=$((params_0-i+1))
VectorAddElement params2 count params_$i
done
fi
# Printing the vector 'params2':
VectorPrint params2
read temp
## Getting the values of 'params2'`s elements and printing them:
if [ -n "$params2_0" ]; then
echo "Printing the elements of the vector 'params2':"
for ((i=1; i<=$params2_0; i++)); do
eval current_elem_value=\"\$params2\_$i\"
echo "params2_$i=\"$current_elem_value\""
done
else
echo "Vector 'params2' is empty!"
fi
read temp
## Creating a two dimensional array ('a'):
for ((i=1; i<=10; i++)); do
VectorAddElement a 0 i
for ((j=1; j<=8; j++)); do
value=$(( 8 * ( i - 1 ) + j ))
VectorAddElementDV a_$i $j $value
done
done
## Manually printing the two dimensional array ('a'):
echo "Printing the two-dimensional array 'a':"
if [ -n "$a_0" ]; then
for ((i=1; i<=$a_0; i++)); do
eval current_vector_lenght=\$a\_$i\_0
if [ -n "$current_vector_lenght" ]; then
for ((j=1; j<=$current_vector_lenght; j++)); do
eval value=\"\$a\_$i\_$j\"
printf "$value "
done
fi
printf "\n"
done
fi
################
### MAIN END ###
################
If each row of the matrix is the same size, then you can simply use a linear array and multiplication.
That is,
a=()
for (( i=0; i<4; ++i )); do
for (( j=0; j<5; ++j )); do
a[i*5+j]=0
done
done
Then your a[2][3] = 3 becomes
a[2*5+3] = 3
This approach might be worth turning into a set of functions, but since you can't pass arrays to or return arrays from functions, you would have to use pass-by-name and sometimes eval. So I tend to file multidimensional arrays under "things bash is simply Not Meant To Do".
One can simply define two functions to write ($4 is the assigned value) and read a matrix with arbitrary name ($1) and indexes ($2 and $3) exploiting eval and indirect referencing.
#!/bin/bash
matrix_write () {
eval $1"_"$2"_"$3=$4
# aux=$1"_"$2"_"$3 # Alternative way
# let $aux=$4 # ---
}
matrix_read () {
aux=$1"_"$2"_"$3
echo ${!aux}
}
for ((i=1;i<10;i=i+1)); do
for ((j=1;j<10;j=j+1)); do
matrix_write a $i $j $[$i*10+$j]
done
done
for ((i=1;i<10;i=i+1)); do
for ((j=1;j<10;j=j+1)); do
echo "a_"$i"_"$j"="$(matrix_read a $i $j)
done
done
Mark Reed suggested a very good solution for 2D arrays (matrix)! They always can be converted in a 1D array (vector). Although Bash doesn't have a native support for 2D arrays, it's not that hard to create a simple ADT around the mentioned principle.
Here is a barebone example with no argument checks, etc, just to keep the solution clear: the array's size is set as two first elements in the instance (documentation for the Bash module that implements a matrix ADT, https://github.com/vorakl/bash-libs/blob/master/src.docs/content/pages/matrix.rst )
#!/bin/bash
matrix_init() {
# matrix_init instance x y data ...
declare -n self=$1
declare -i width=$2 height=$3
shift 3;
self=(${width} ${height} "$#")
}
matrix_get() {
# matrix_get instance x y
declare -n self=$1
declare -i x=$2 y=$3
declare -i width=${self[0]} height=${self[1]}
echo "${self[2+y*width+x]}"
}
matrix_set() {
# matrix_set instance x y data
declare -n self=$1
declare -i x=$2 y=$3
declare data="$4"
declare -i width=${self[0]} height=${self[1]}
self[2+y*width+x]="${data}"
}
matrix_destroy() {
# matrix_destroy instance
declare -n self=$1
unset self
}
# my_matrix[3][2]=( (one, two, three), ("1 1" "2 2" "3 3") )
matrix_init my_matrix \
3 2 \
one two three \
"1 1" "2 2" "3 3"
# print my_matrix[2][0]
matrix_get my_matrix 2 0
# print my_matrix[1][1]
matrix_get my_matrix 1 1
# my_matrix[1][1]="4 4 4"
matrix_set my_matrix 1 1 "4 4 4"
# print my_matrix[1][1]
matrix_get my_matrix 1 1
# remove my_matrix
matrix_destroy my_matrix
For simulating a 2-dimensional array, I first load the first n-elements (the elements of the first column)
local pano_array=()
i=0
for line in $(grep "filename" "$file")
do
url=$(extract_url_from_xml $line)
pano_array[i]="$url"
i=$((i+1))
done
To add the second column, I define the size of the first column and calculate the values in an offset variable
array_len="${#pano_array[#]}"
i=0
while [[ $i -lt $array_len ]]
do
url="${pano_array[$i]}"
offset=$(($array_len+i))
found_file=$(get_file $url)
pano_array[$offset]=$found_file
i=$((i+1))
done
The below code will definitely work provided if you are working on a Mac you have bash version 4. Not only can you declare 0 but this is more of a universal approach to dynamically accepting values.
2D Array
declare -A arr
echo "Enter the row"
read r
echo "Enter the column"
read c
i=0
j=0
echo "Enter the elements"
while [ $i -lt $r ]
do
j=0
while [ $j -lt $c ]
do
echo $i $j
read m
arr[${i},${j}]=$m
j=`expr $j + 1`
done
i=`expr $i + 1`
done
i=0
j=0
while [ $i -lt $r ]
do
j=0
while [ $j -lt $c ]
do
echo -n ${arr[${i},${j}]} " "
j=`expr $j + 1`
done
echo ""
i=`expr $i + 1`
done

until item does not exists in array do in bash

I have below part of the code
jmsPort=61015
jmsPorts=(61018 61016 61017)
until [ $jmsPorts -ne $jmsPort ]; do
jmsPort=$(expr $jmsPort + 1)
done
The iteration of the loop is only once in this case, how can use do this
until the jmsPorts array does not contain the jmsPort do (jmsPort + 1)
the goal that I want to achieve is
If the jmsPort matches an element in the array:
It should do + 1 to the jmsPort until the jmsPort does not match an element in the jmsPorts array
If the jmsPort is not in the array:
it should choose the jmsPort
I have done this using PowerShell but can't do it in Bash.
the syntax in PowerShell is
$jmsPort = 61016
$jmsPorts = #(61018, 61016, 61017)
do
{
$jmsPort = $jmsPort + 1
}
until ($jmsPorts -notcontains $jmsPort )
Remember that
jmsPorts=() # jmsPorts is an array
To update the array, initially(before the edit) you've put
jmsPorts+=$(echo $i | grep -oP '\d+') # Wrong
It should've been
jmsPorts+=( "$(echo $i | grep -oP '\d+')" ) #mind the outer brackets.
Now to check if jmsPort doesn't match any element in jmsPorts array do
validator(){
flag=0 # Assuming the element is not present
for i in "${jmsPorts[#]}"
do
if [ "$1" -eq "$i" ]
then
flag=1 # Element is present
fi
done
}
while validator "$jmsPort" && [ "$flag" -eq 1 ]
do
((jmsPort++))
done
echo "Port Chosen : $jmsPort"
# This port doesn't match any value in the array.
Warning : Ports 1-65535 are available, and ports in range 1-1023 are the privileged ones. On more complicated scenatios an extra check [ $jmsPort -lt 65535 ] might be necessary.

In array operator in bash

Is there a way to test whether an array contains a specified element?
e.g., something like:
array=(one two three)
if [ "one" in ${array} ]; then
...
fi
A for loop will do the trick.
array=(one two three)
for i in "${array[#]}"; do
if [[ "$i" = "one" ]]; then
...
break
fi
done
Try this:
array=(one two three)
if [[ "${array[*]}" =~ "one" ]]; then
echo "'one' is found"
fi
I got an function 'contains' in my .bashrc-file:
contains ()
{
param=$1;
shift;
for elem in "$#";
do
[[ "$param" = "$elem" ]] && return 0;
done;
return 1
}
It works well with an array:
contains on $array && echo hit || echo miss
miss
contains one $array && echo hit || echo miss
hit
contains onex $array && echo hit || echo miss
miss
But doesn't need an array:
contains one four two one zero && echo hit || echo miss
hit
I like using grep for this:
if echo ${array[#]} | grep -qw one; then
# "one" is in the array
...
fi
(Note that both -q and -w are non-standard options to grep: -w tells it to work on whole words only, and -q ("quiet") suppresses all output.)
array="one two three"
if [ $(echo "$array" | grep one | wc -l) -gt 0 ] ;
then echo yes;
fi
If that's ugly, you could hide it away in a function.
if you just want to check whether an element is in array, another approach
case "${array[#]/one/}" in
"${array[#]}" ) echo "not in there";;
*) echo "found ";;
esac
In_array() {
local NEEDLE="$1"
local ELEMENT
shift
for ELEMENT; do
if [ "$ELEMENT" == "$NEEDLE" ]; then
return 0
fi
done
return 1
}
declare -a ARRAY=( "elem1" "elem2" "elem3" )
if In_array "elem1" "${ARRAY[#]}"; then
...
A nice and elegant version of the above.
in_array() {
local needle=$1 el
shift
for el in "$#"; do
if [ "$el" = "$needle" ]; then
return 0
fi
done
return 1
}
if in_array 1 1 2 3; then
echo true
else
echo false
fi
# alternatively
a=(1 2 3)
if in_array 1 "${a[#]}"; then
...
OPTIONS=('-q','-Q','-s','-S')
find="$(grep "\-q" <<< "${OPTIONS[#]}")"
if [ "$find" = "${OPTIONS[#]}" ];
then
echo "arr contains -q"
fi

Resources