Bash - How to check if value doesn't exist in array? - arrays

Folks,
I have an array, ex,
declare -a arr=("crm" "hr" "pos")
I need to output error if the passed value doesn't exist in this array
I'm trying this use below snippet but it prints "No match found" for any value
match=0
for i in "${arr[#]}"; do
if ! [[ $2 == "$i" ]]; then
match=1
break
fi
done
if [[ $match = 1 ]]; then
echo "No match found"
fi
Any idea how to loop in array and popup error if value doesn't exist ?

There are already answers about your question see check value is in an array, but a fix/idea for your specific approach is something like.
#!/usr/bin/env bash
declare -a arr=("crm" "hr" "pos")
match=0
for i in "${arr[#]}"; do
if ! [[ $2 == "$i" ]]; then
((match++))
fi
done
if (( match == ${#arr[*]} )); then
printf >&2 "No match found\n"
fi
The above script increments match every time the test inside the for loop is true, and in the end compare match with the length of the array ${#arr[*]}. A more verbose output is to put set -x on after the shebang and add a $ sign to the variable match (which is not required) inside the (( )).
if (( $match == ${#arr[*]} )); then
Your original approach always breaks the loop when it does not have a match, doing so will not continue the loop.

You reversed the logic. Just leave out the ! and swap the final test:
match=0
for i in "${arr[#]}"; do
if [[ $2 == "$i" ]]; then
match=1
break
fi
done
if [[ $match = 0 ]]; then
echo "No match found"
fi

This doesn't require a loop.
c.f. if a 2 strings BOTH present in 1 array in a bash script
$: x=bar
$: [[ " ${arr[*]} " =~ " $x " ]] && echo found || echo nope
nope
$: x=hr
$: [[ " ${arr[*]} " =~ " $x " ]] && echo found || echo nope
found
or
$: match=0
$: set -- foo bar
$: [[ " ${arr[*]} " =~ " $2 " ]] && match=1
$: echo match
0
$: set -- foo crm
$: [[ " ${arr[*]} " =~ " $2 " ]] && match=1
$: echo match
1

The logic seems incorrect.
If it does not match, you break the loop.
If it does match, the loop goes to the next iteration. Then it becomes unmatched and finally breaks the loop
declare -a arr=("crm" "hr" "pos")
not_match=0
for i in "${arr[#]}"; do
if [ "$2" = "$i" ]; then
not_match=1
break
fi
done
if [ $not_match -eq 0 ]; then
echo "No match found"
fi

Related

Compare array elements with an element

I am trying to compare an array with a value and print an error statement if the match is not found.
arraylist="$(ls new-dir/ | cut -d' ' -f1)"
For example, this stores values such as small, large and medium which are the files present in new-dir.
The value to be compared with will be entered by the user viz. var
I have tried something like following:
(for i in "${arraylist[#]}"; do [[ "$i" == "$var"]] && exit 0; done) && echo "found" || echo "not found"
Also tried, however, doesn't work:
arraylist="$(ls new-dir/ | cut -d' ' -f1)"
count=0
for((i=0; i<${#arraylist[#]}; i++)); do
if [ "$arraylist[$i]" == "$var" ] ; then
count=1
fi
done
if [ $count -eq 0 ]; then
echo "Not found"
fi
Is there any other way to do this comparison?
This code works. Just replace arraylist with your array.
arraylist=('hello' 'world')
var='hello'
count=0
for i in "${arraylist[#]}"
do
if [ "$i" == "$var" ]; then
count=1
fi
done
if [ $count -eq 0 ]; then
echo "Not found"
fi
To extract the first "word" from each filename, I'd do
declare -A prefixes
cd new-dir
for file in *; do
read -r prefix _ <<< "$file"
prefixes["$prefix"]=1
done
cd -
then to look for a match:
# $var is somehow provided
result=""
for p in "${!prefixes[#]}"; do
if [[ $p == "$var" ]]; then
result=found
break
fi
done
echo "${result:-not found}"
This requires bash v4.0+ for the use of an associative array.
If you have bash v4.3+ you can do
[[ -v prefixes[$var] ]] && echo found || echo not found
Alternately:
shopt -s nullglob
files=( new-dir/"$var "* )
(( ${#files[#]} > 0 )) && echo found || echo not found
This is what shell programming is about. Keep it simple :
Array1=( "item1" "item2" "item3" "item-4" )
var="item3"
count=$(echo ${Array1[#]} | tr ' ' '\n' | awk '$1 == "'"$var"'"{print $0}' | wc -l)
[ $count -eq 0 ] && echo "Not found" || echo "found"
The arraylist might be an array with just one index.
The command:
arraylist="$(ls new-dir/ | cut -d' ' -f1)"
might just assign whole "ls new-dir" output to ${arraylist[0]}
You can check the index value in an array by echo ${arraylist[1]} (to confirm).
Try changing the command to generate an array for a list of files/dirs to:
arraylist=($(ls new-dir/))
The complete script:
arraylist=($(ls new-dir/))
(for i in "${arraylist[#]}"; do [[ "$i" == "$var" ]] && exit 0; done) && echo "found" || echo "not found"
You can also ignore the whole array assignment and just use subshell-command in for loop, eg:
(for i in $(ls -1 new-dir/); do [[ "$i" == "$var" ]] && exit 0; done) && echo "found" || echo "not found"
Remember, no quotes " around $(ls -1 new-dir/)
Another one-liner without any array or loops:
ls -1 new-dir/ | grep -q "$var" && echo "found" || echo "not found"
Edit:
As #M. Nejat Aydin suggested, use [[ -e new-dir/$var ]] && echo "found" || echo "Not found" and do not use ls in the script.

Appending to an array in bash, why this isn't working?

I'm trying from a list of files with a pattern correctly matched by regex to check whether this value is in my array, if not, append it.
Unfortunately, this code that I build up inspired by some stack overflow post doesn't work (nothing is happened, the =~ doesn't seem to find the bash_rematch, and also it doesn't output anything?
sample_array=() #creating the array
for context_files in data/*.txt.gz # checking all the different samples id we have
do
[[ $context_files =~ SL[0-9]{6} ]]
echo 'context file:' "$context_files"
echo 'rematch:' "${BASH_REMATCH[0]}"
if ! [[ " ${sample_array[*]} " =~ (^|[[:space:]])"${BASH_REMATCH[0]}"($|[[:space:]]) ]]; then
echo 'condition matched'
echo 'rematch:' "${BASH_REMATCH[0]}"
sample_array+=(" ${BASH_REMATCH[0]} ")
fi
done
echo "${sample_array[*]}"
replacing this code by
sample_array=() #creating the array
for context_files in data/*.txt.gz # checking all the different samples id we have
do
[[ $context_files =~ SL[0-9]{6} ]]
echo 'context file:' "$context_files"
echo 'rematch:' "${BASH_REMATCH[0]}"
if ! [[ " ${sample_array[*]} " == "${BASH_REMATCH[0]}" ]]; then
echo 'condition matched'
echo 'rematch:' "${BASH_REMATCH[0]}"
sample_array+=(" ${BASH_REMATCH[0]} ")
fi
done
echo "${sample_array[*]}"
will this time add all the variable
output :
A B A B A B
I probably don't get something in how the if is managed and/or how the regex lookup in a bash array is to be made but I'd gladly get some help!
The second match is negated, so in order to enter the then part, the match needs to fail. A failed match resets $BASH_REMATCH.
#! /bin/bash
sample_array=()
for context_files in data/SL{111111,222222,333333,111111,222222}.txt.gz ; do
[[ $context_files =~ SL[0-9]{6} ]]
match=${BASH_REMATCH[0]}
echo 'context file:' "$context_files"
echo 'rematch:' "$match"
if ! [[ " ${sample_array[*]} " =~ (^|[[:space:]])"$match"($|[[:space:]]) ]]; then
echo 'condition matched'
echo 'rematch:' "$match"
sample_array+=(" $match ")
fi
done
echo "${sample_array[*]}"
Here is a completely alternative solution in bash-style like John Kugelman suggested:
printf %s\\n data/*.txt.gz | grep -Eo 'SL[0-9]{6}' | sort -u
If you need the results in an array, use mapfile:
mapfile -t array <(printf %s\\n data/*.txt.gz | grep -Eo 'SL[0-9]{6}' | sort -u)

Bash, compare string to arrayvalue

i try to match a parameter with some array content. At the if clauses should be true, but it wont be.
At the output before compare i got this:
VAL: drei_01 AND: drei
#!/bin/bash
array=( null_01 eins_01 zwei_01 drei_01 vier_01 )
lookarr() {
maxc=${#array[#]}
mbool=0
for((i=0; i<$maxc; i++))
do
val=${array[$i]}
echo "VAL: $val AND: $1"
if [[ $1 == *" $val "* ]]; then
echo "TESTENTRY1"
#do something
mbool=1
break
fi
done
if [[ $mbool -eq 0 ]]; then
echo "TESTENTRY2"
#do something else
fi
}
lookarr drei
thanks
Your if statement isn't matching because it is back-to-front and has extra spaces. For drei to match drei_01 you can replace your if statement with:
if [[ "$val" == *"$1"* ]]; then

Bash, how to iterate through an array once & append to end of line?

I am tasked with writing a script that analyzes code and attaches a comment with #Loopn or #Selection n that corresponds with the correct statements.
echo "enter full file name: "
read file
getArray(){
arr=()
while IFS= read -r line
do
arr+=("$line")
done < "$1"
}
getArray $file
echo "What file looks like before editing"
printf "%s\n" "${arr[#]}" #Test function to see if array works (it does)
#Declare variables
x=1
y=1
#Start main loop
for (( i=0; i<${#arr[#]}; i++ ));
do
if [[ "${arr[$i]}" == "while" ]] || [[ "${arr[$i]}" == "until" ]]
then sed -i 's/$/ #Loop'$x'/' $file && let "x++"
continue
elif [[ "${arr[$i]}" == "for" ]]
then sed -i 's/$/ #Loop'$x'/' $file && let "x++"
continue
elif [[ "${arr[$i]}" == "break" ]] || [[ "${arr[$i]}" == "done" ]]
then sed -i 's/$/ #Loop'$x'/' $file && let "x--"
continue
elif [[ "${arr[$i]}" == "if" ]] || [[ "${arr[$i]}" == "case" ]]
then sed -i 's/$/ #Selection'$y'/' $file && let "y++"
continue
elif [[ "${arr[$i]}" == "fi" ]] || [[ "${arr[$i]}" == "esac" ]]
then sed -i 's/$/ #Selection'$y'/' $file && let "y--"
continue
else
continue
fi
done < $file
Obviously I'm a newbie in bash, and my loop logic/language usage might be a bit wonky. Can anyone help? Right now the output makes it seem like I am iterating through the array more than once and Sed appends additional text per line.
In case it wasn't clear: each array element is a line of strings; if an array element contains while || for || until then it adds a #loop n and with each of the corresponding break or done, it adds the same #loop n. And likewise for if and case and fi esac except it adds #selection n.
Sample Input:
Before
Final=$(date -d "2016-12-15 14:00" "+%j")
while true ; do
Today=$(date "+%j")
Days=$((Final - Today))
if (( Days >= 14 )) ; then
echo party
elif (( Days >= 2 )) ; then
echo study
elif (( Days == 1 )) ; then
for Count in 1 2 3
do
echo panic
done
else
break
fi
sleep 8h
done
Expected Output:
After
Final=$(date -d "2016-12-15 14:00" "+%j")
while true ; do # loop 1
Today=$(date "+%j")
Days=$((Final - Today))
if (( Days >= 14 )) ; then # selection 1
echo party
elif (( Days >= 2 )) ; then
echo study
elif (( Days == 1 )) ; then
for Count in 1 2 3 # loop 2
do
echo panic
done # loop 2
else
break
fi # selection 1
sleep 8h
done # loop 1
Right now the output makes it seem like I am iterating through the array more than once and Sed appends additional text per line.
This is because the comment to be attached to one line is appended to each line of the file, since no line number is specified for the sed substitute commands in your script. There surely are more efficient solutions, but prepending the corresponding line number is sufficient.
Though your script is quite close to working, two more problems have to be addressed. One is that the == expressions you use to test for the keywords match only if the whole line contains nothing else than the keyword (not even leading space); to allow for indentation, =~ with an appropriate regular expression is useful. The other problem is the counting of the nesting depth (including the simple, but special case of break, where the depth remains unchanged); this seems more easy if we start at depth 0. So, your main loop could be:
x=0
y=0
#Start main loop
for (( i=0; i<${#arr[#]}; i++ ))
do let l=1+i # line numbers start at 1
if [[ "${arr[$i]}" =~ ^[$IFS]*(while|until|for) ]]
then sed -i $l"s/$/ #Loop$((++x))/" $file
elif [[ "${arr[$i]}" =~ ^[$IFS]*break ]]
then sed -i $l"s/$/ #Loop$x/" $file # no x-- here!
elif [[ "${arr[$i]}" =~ ^[$IFS]*done ]]
then sed -i $l"s/$/ #Loop$((x--))/" $file
elif [[ "${arr[$i]}" =~ ^[$IFS]*(if|case) ]]
then sed -i $l"s/$/ #Selection$((++y))/" $file
elif [[ "${arr[$i]}" =~ ^[$IFS]*(fi|esac) ]]
then sed -i $l"s/$/ #Selection$((y--))/" $file
fi
done <$file
You can test this;
echo "enter full file name: "
read file
getArray(){
arr=()
while IFS= read -r line
do
arr+=("$line")
done < "$1"
}
getArray $file
echo "What file looks like before editing"
printf "%s\n" "${arr[#]}" #Test function to see if array works (it does)
#Declare variables
x=1
y=1
#Start main loop
for (( i=0; i<${#arr[#]}; i++ ));
do
#echo $i "====" ${arr[$i]}
#echo "-------"
#echo "x"$x
if [[ "${arr[$i]}" == "while"* ]] || [[ "${arr[$i]}" == "until" ]]
then
sed -i "$((i+1))s/$/ #Loop $x/" $file
let "x++"
continue;
fi
if [[ "${arr[$i]}" == *"for"* ]]
then sed -i "$((i+1))s/$/ #Loop'$x'/" $file && let "x++"
continue
fi
if [[ "${arr[$i]}" == *"done"* ]]
then sed -i "$((i+1))s/$/ #Loop'$((x-1))'/" $file
continue
fi
if [[ "${arr[$i]}" == *"if"* ]] || [[ "${arr[$i]}" == *"case"* ]]
then
if [[ "${arr[$i]}" != *"elif"* ]]
then
sed -i "$((i+1))s/$/ #Selection'$y'/" $file && let "y++"
fi
continue
fi
if [[ "${arr[$i]}" == *"fi"* ]] || [[ "${arr[$i]}" == *"esac"* ]]
then sed -i "$((i+1))s/$/ #Selection'$((y-1))'/" $file
continue
fi
done < $file

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