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.
Related
thanks for your help.
What a I doing wrong, here ? I want the numeric answer to be used in a loop but it does not return what I expected :
if [ "$#" -eq 0 ]; then
echo -n "Enter the number: "
read answer
else
answer=( "$#" )
fi
for i in {1..$answer} ; do echo ${i}; done
When executed, I have this:
me#local misc]$ ./test.sh
Enter the number: 5
{1..5}
I expected the echo to return 1 2 3 4 5
You can also do it like:
if [ "$#" -eq 0 ]; then
echo -n "Enter the number: "
read answer
else
answer=( "$#" )
fi
for i in $(seq 1 $answer) ; do echo ${i}; done
With no proposed answer I decided to switch to bash with that syntax that works (though it bothers me)
for (( i=1 ; i<=${answer} ; i++));
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.
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
This question already has answers here:
Check if a Bash array contains a value
(41 answers)
Closed 2 years ago.
I was wondering if there is an efficient way to check if an element is present within an array in Bash? I am looking for something similar to what I can do in Python, like:
arr = ['a','b','c','d']
if 'd' in arr:
do your thing
else:
do something
I've seen solutions using associative array for bash for Bash 4+, but I am wondering if there is another solution out there.
Please understand that I know the trivial solution is to iterate in the array, but I don't want that.
You could do:
if [[ " ${arr[*]} " == *" d "* ]]; then
echo "arr contains d"
fi
This will give false positives for example if you look for "a b" -- that substring is in the joined string but not as an array element. This dilemma will occur for whatever delimiter you choose.
The safest way is to loop over the array until you find the element:
array_contains () {
local seeking=$1; shift
local in=1
for element; do
if [[ $element == "$seeking" ]]; then
in=0
break
fi
done
return $in
}
arr=(a b c "d e" f g)
array_contains "a b" "${arr[#]}" && echo yes || echo no # no
array_contains "d e" "${arr[#]}" && echo yes || echo no # yes
Here's a "cleaner" version where you just pass the array name, not all its elements
array_contains2 () {
local array="$1[#]"
local seeking=$2
local in=1
for element in "${!array}"; do
if [[ $element == "$seeking" ]]; then
in=0
break
fi
done
return $in
}
array_contains2 arr "a b" && echo yes || echo no # no
array_contains2 arr "d e" && echo yes || echo no # yes
For associative arrays, there's a very tidy way to test if the array contains a given key: The -v operator
$ declare -A arr=( [foo]=bar [baz]=qux )
$ [[ -v arr[foo] ]] && echo yes || echo no
yes
$ [[ -v arr[bar] ]] && echo yes || echo no
no
See 6.4 Bash Conditional Expressions in the manual.
Obvious caveats aside, if your array was actually like the one above, you could do
if [[ ${arr[*]} =~ d ]]
then
do your thing
else
do something
fi
Initialize array arr and add elements
set variable to search for SEARCH_STRING
check if your array contains element
arr=()
arr+=('a')
arr+=('b')
arr+=('c')
SEARCH_STRING='b'
if [[ " ${arr[*]} " == *"$SEARCH_STRING"* ]];
then
echo "YES, your arr contains $SEARCH_STRING"
else
echo "NO, your arr does not contain $SEARCH_STRING"
fi
If array elements don't contain spaces, another (perhaps more readable) solution would be:
if echo ${arr[#]} | grep -q -w "d"; then
echo "is in array"
else
echo "is not in array"
fi
array=("word" "two words") # let's look for "two words"
using grep and printf:
(printf '%s\n' "${array[#]}" | grep -x -q "two words") && <run_your_if_found_command_here>
using for:
(for e in "${array[#]}"; do [[ "$e" == "two words" ]] && exit 0; done; exit 1) && <run_your_if_found_command_here>
For not_found results add || <run_your_if_notfound_command_here>
As bash does not have a built-in value in array operator and the =~ operator or the [[ "${array[#]" == *"${item}"* ]] notation keep confusing me, I usually combine grep with a here-string:
colors=('black' 'blue' 'light green')
if grep -q 'black' <<< "${colors[#]}"
then
echo 'match'
fi
Beware however that this suffers from the same false positives issue as many of the other answers that occurs when the item to search for is fully contained, but is not equal to another item:
if grep -q 'green' <<< "${colors[#]}"
then
echo 'should not match, but does'
fi
If that is an issue for your use case, you probably won't get around looping over the array:
for color in "${colors[#]}"
do
if [ "${color}" = 'green' ]
then
echo "should not match and won't"
break
fi
done
for color in "${colors[#]}"
do
if [ "${color}" = 'light green' ]
then
echo 'match'
break
fi
done
Here's another way that might be faster, in terms of compute time, than iterating. Not sure. The idea is to convert the array to a string, truncate it, and get the size of the new array.
For example, to find the index of 'd':
arr=(a b c d)
temp=`echo ${arr[#]}`
temp=( ${temp%%d*} )
index=${#temp[#]}
You could turn this into a function like:
get-index() {
Item=$1
Array="$2[#]"
ArgArray=( ${!Array} )
NewArray=( ${!Array%%${Item}*} )
Index=${#NewArray[#]}
[[ ${#ArgArray[#]} == ${#NewArray[#]} ]] && echo -1 || echo $Index
}
You could then call:
get-index d arr
and it would echo back 3, which would be assignable with:
index=`get-index d arr`
FWIW, here's what I used:
expr "${arr[*]}" : ".*\<$item\>"
This works where there are no delimiters in any of the array items or in the search target. I didn't need to solve the general case for my applicaiton.
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