until item does not exists in array do in bash - arrays

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.

Related

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

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
)

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

"Push" onto bash associative array

I'm trying to run a script for all files in a directory with a common ID.
ls -1 *.vcf
a1.sourceA.vcf
a1.sourceB.vcf
a1.sourceC.vcf
a2.sourceA.vcf
a2.sourceB.vcf
a2.sourceC.vcf
a3.sourceA.vcf
a3.sourceC.vcf
The ID in each case precedes the first . (a1, a2 or a3) and for each ID I want to have all the sources for that ID in an associative array, keyed by the ID, e.g.;
a1 => [a1.sourceA.vcf, a1.sourceB.vcf, a1.sourceC.vcf]
I've attempted this as follows:
for file in $(ls *.vcf | sort)
do
id=$(echo $file | cut -d '.' -f 1)
vcfs[$id]+=$file
done
for i in "${!vcfs[#]}"
do
echo "key : $i"
echo "value: ${vcfs[$i]}"
echo " "
done
But I can't figure out how to get it working.
In Perl I would push values onto a hash of arrays in the loop:
push #{$vcfs{$id}}, $file;
to give me a data structure like this:
'a1' => [
'a1.sourceA.vcf',
'a1.sourceB.vcf',
'a1.sourceC.vcf'
],
'a3' => [
'a3.sourceA.vcf',
'a3.sourceC.vcf'
],
'a2' => [
'a2.sourceA.vcf',
'a2.sourceB.vcf',
'a2.sourceC.vcf'
]
How can I achieve this in bash?
From another answer given in question's comments
unset a1 a2 a3
function push {
local arr_name=$1
shift
if [[ $(declare -p "$arr_name" 2>&1) != "declare -a "* ]]
then
declare -g -a "$arr_name"
fi
declare -n array=$arr_name
array+=($#)
}
for file in *.vcf; do [[ -e $file ]] && push "${file%%.*}" "$file"; done
(IFS=,;echo "${a1[*]}")
(IFS=,;echo "${a2[*]}")
(IFS=,;echo "${a3[*]}")
But depending on needs maybe for with pattern is sufficient
for file in a1.*.vcf; do ... ; done
Finally $(ls ) must not be used in for loops as seen in other answers.
Why you shouldn't parse the output of ls

Remove (not just unset) multiple strings from an array without knowing their positions

Say I have arrays
a1=(cats,cats.in,catses,dogs,dogs.in,dogses)
a2=(cats.in,dogs.in)
I want to remove everything from a1 that matches the strings in a2 after removing ".in" , in addition to the ones that match completely(including ".in").
So from a1, I want to remove cats, cats.in, dogs, dogs.in, but not catses or dogses.
I think I'll have to do this in 2 steps. I found how to cut the ".in" away:
for elem in "${a2[#]}" ; do
var="${elem}"
len="${#var}"
pref=${var:0:len-3}
done
^ this gives me "cats" and "dogs"
What command do I need to add to the loop remove each elem from a1?
Seems to me that the easiest way to solve this is with nested for loops:
#!/usr/bin/env bash
a1=(cats cats.in catses dogs dogs.in dogses)
a2=(cats.in dogs.in)
for x in "${!a1[#]}"; do # step through a1 by index
for y in "${a2[#]}"; do # step through a2 by content
if [[ "${a1[x]}" = "$y" || "${a1[x]}" = "${y%.in}" ]]; then
unset a1[x]
fi
done
done
declare -p a1
But depending on your actual data, the following might be better, using two separate for loops instead of nesting.
#!/usr/bin/env bash
a1=(cats cats.in catses dogs dogs.in dogses)
a2=(cats.in dogs.in)
# Flip "a2" array to "b", stripping ".in" as we go...
declare -A b=()
for x in "${!a2[#]}"; do
b[${a2[x]%.in}]="$x"
done
# Check for the existence of the stripped version of the array content
# as an index of the associative array we created above.
for x in "${!a1[#]}"; do
[[ -n "${b[${a1[x]%.in}]}" ]] && unset a1[$x] a1[${x%.in}]
done
declare -p a1
The advantage here would be that instead of looping through all of a2 for each item in a1, you just loop once over each array. Down sides might depend on your data. For example, if contents of a2 are very large, you might hit memory limits. Of course, I can't know that from what you included in your question; this solution works with the data you provided.
NOTE: this solution also depends on an associative array, which is a feature introduced to bash in version 4. If you're running an old version of bash, now might be a good time to upgrade. :)
This is the solution I went with:
for elem in "${a2[#]}" ; do
var="${elem}"
len="${#var}"
pref=${var:0:len-3}
#set 'cats' and 'dogs' to ' '
for i in ${!a1[#]} ; do
if [ "${a1[$i]}" = "$pref" ] ; then
a1[$i]=''
fi
#set 'cats.in' and 'dogs.in' to ' '
if [ "${a1[$i]}" = "$var" ] ; then
a1[$i]=''
fi
done
done
Then I created a new array from a1 without the ' ' elements
a1new=( )
for filename in "${a1[#]}" ; do
if [[ $a1 != '' ]] ; then
a1new+=("${filename}")
fi
done
A naive approach would be:
#!/bin/bash
# Checkes whether a value is in an array.
# Usage: "$value" "${array[#]}"
inarray () {
local n=$1 h
shift
for h in "$#";do
[[ $n = "$h" ]] && return
done
return 1
}
a1=(cats cats.in catses dogs dogs.in dogses)
a2=(cats.in dogs.in)
result=()
for i in "${a1[#]}";do
if ! inarray "$i" "${a2[#]}" && ! inarray "$i" "${a2[#]%.in}"; then
result+=("$i")
fi
done
# Checking.
printf '%s\n' "${result[#]}"
If you only want to print the values to stdout, you might instead want to use comm:
comm -23 <(printf '%s\n' "${a1[#]}"|sort -u) <(printf '%s\n' "${a2[#]%.in}" "${a2[#]}"|sort -u)

bash - pulling content from array via index

I am having trouble with a script that prints out the index and items of the array and then allows the user to input an index that will print out that specific item of the array.
array=(abc def ghi)
i=0
while [ $i -lt ${#array[*]} ]; do
echo "[$i] ${array[$i]}"
i=$(($i+1));
done
echo -e "select an index: "; read answer
#this is the part that is troubling me
for index in ${!array[*]}; do
if [[ $answer == $index ]]; then
echo ${array[$index]}
break
else
echo "invalid"
break
fi
done
so if the user enters 0, it should print abc. 1 would be def etc. It currently only works for index 0.
Couple of problems in there that I can find:
you need a space right after $item to separate from the ]
$item should probably be $index
most importantly, remove the break after the "echo invalid" which is the reason the loop exits right after testing against '0' and doesn't check for the next array indexes. Using a flag then you should control if the answer matched any of the valid indexes, otherwise print the "invalid" message. I used a very explicit string test there for clarity.
A running code should look like:
array=(abc def ghi)
i=0
while [ $i -lt ${#array[*]} ]; do
echo "[$i] ${array[$i]}"
i=$(($i+1));
done
echo -e "select an index: "; read answer
for index in ${!array[*]}; do
found="FALSE"
if [[ "$answer" == "$index" ]]; then
found="TRUE"
echo "${array[$index]}"
break
fi
done
if [ "$found" == "FALSE" ]; then
echo "Invalid input $answer"
fi

Resources