How to same named argument into an array - arrays

I am new to Shell scripting. I've requriment to replace few string values in a file. They have to be read from command line and shell script looks like below.
test.sh --old-value yahoo.com --new-value ibibo.io --old-value xxxxxx --new-value yyy --exclude aa bb cc
Now, I want read --old-values into one array, --new-values into other array and --exclude into another array.
i am trying below approach.
while [[ $# -gt 1 ]]
do
key="$1"
case $key in
--old-value)
OLDVALUE="$2"
shift # past argument
;;
--new-value)
NEWVALUE="$2"
shift # past argument
;;
--exclude)
EXCLUDEFILETYPES=("$#")
shift
;;
*)
# unknown option
;;
esac
shift # past argument or value
done
But this read the old value into OLDVALUE. I have to read both --old-values into an array.
Can someone help on how to achieve this usecase?

#!/bin/bash
# declare arrays
old=(); new=(); exclude=()
while [[ $# -gt 1 ]]; do
key="$1"
value="$2"
[[ $key == --old-value ]] && old+=("$value")
[[ $key == --new-value ]] && new+=("$value")
[[ $key == --exclude ]] && shift && exclude+=("$#")
shift 2
done
# show content of arrays
declare -p old new exclude
I assume --exclude aa bb cc are last arguments.
Example: ./test.sh --old-value yahoo.com --new-value ibibo.io --old-value xxxxxx --new-value yyy --exclude aa bb cc
Output:
declare -a old='([0]="yahoo.com" [1]="xxxxxx")'
declare -a new='([0]="ibibo.io" [1]="yyy")'
declare -a exclude='([0]="aa" [1]="bb" [2]="cc")'

Another variant:
while (( $# )); do
case "$1" in
--old-value) old+=("$2") ;;
--new-value) new+=("$2") ;;
--exclude) shift; exc=("$#") ; break ;;
--*) echo "bad arg" ; exit 1 ;;
esac
shift;shift
done
printf "old: %s\n" "${old[#]}"
printf "new: %s\n" "${new[#]}"
printf "exc: %s\n" "${exc[#]}"
output
old: yahoo.com
old: xxxxxx
new: ibibo.io
new: yyy
exc: aa
exc: bb
exc: cc
Of course, this still has problems. For example if the script is called as
--old-value yahoo.com --new-value --old-value xxxxxx --new-value yyy --exclude aa bb cc
note the missing new value argument. In such case the result will be of course wrong...
old: yahoo.com
new: --old-value
exc:
handling such error cases needs much more complicated state-handling.

Related

Check if JSON array contains string passed by variable value [duplicate]

In Bash, what is the simplest way to test if an array contains a certain value?
This approach has the advantage of not needing to loop over all the elements (at least not explicitly). But since array_to_string_internal() in array.c still loops over array elements and concatenates them into a string, it's probably not more efficient than the looping solutions proposed, but it's more readable.
if [[ " ${array[*]} " =~ " ${value} " ]]; then
# whatever you want to do when array contains value
fi
if [[ ! " ${array[*]} " =~ " ${value} " ]]; then
# whatever you want to do when array doesn't contain value
fi
Note that in cases where the value you are searching for is one of the words in an array element with spaces, it will give false positives. For example
array=("Jack Brown")
value="Jack"
The regex will see "Jack" as being in the array even though it isn't. So you'll have to change IFS and the separator characters on your regex if you want still to use this solution, like this
IFS="|"
array=("Jack Brown${IFS}Jack Smith")
value="Jack"
if [[ "${IFS}${array[*]}${IFS}" =~ "${IFS}${value}${IFS}" ]]; then
echo "true"
else
echo "false"
fi
unset IFS # or set back to original IFS if previously set
This will print "false".
Obviously this can also be used as a test statement, allowing it to be expressed as a one-liner
[[ " ${array[*]} " =~ " ${value} " ]] && echo "true" || echo "false"
Below is a small function for achieving this. The search string is the first argument and the rest are the array elements:
set +e #otherwise the script will exit on error
containsElement () {
local e match="$1"
shift
for e; do [[ "$e" == "$match" ]] && return 0; done
return 1
}
A test run of that function could look like:
$ array=("something to search for" "a string" "test2000")
$ containsElement "a string" "${array[#]}"
$ echo $?
0
$ containsElement "blaha" "${array[#]}"
$ echo $?
1
One-line solution
printf '%s\0' "${myarray[#]}" | grep -F -x -z -- 'myvalue'
Explanation
The printf statement prints each element of the array, delimited by null characters.
The grep statement uses the following flags to match an item that contains exactly the string given as myvalue (no more, no less):
-z/--null-data - Lines are terminated by a zero byte instead of a newline.
-F/--fixed-strings - Interpret PATTERNS as fixed strings, not regular expressions.
-x/--line-regexp - Select only those matches that exactly match the whole line.
-- - marks the end of command-line options, making Grep process "myvalue" as a non-option argument even if it starts with a dash
Why do we use a null byte \0 instead of a newline \n? Your array may actually contain newlines within its elements. (If you know that it doesn't, feel free to drop the -z grep option and substitude %s\n as your first printf arg.)
Usage
To put this into an if ... then statement:
if printf '%s\0' "${myarray[#]}" | grep -Fxqz -- 'myvalue'; then
# ...
fi
I added a -q flag to the grep expression so that it won't print matches; it will just treat the existence of a match as "true."
Update: Thanks, presto8, for pointing out the --line-regexp flag. Thanks, Tino, for pointing out the case where newlines can exist within array items.
for i in "${array[#]}"
do
if [ "$i" -eq "$yourValue" ] ; then
echo "Found"
fi
done
For strings:
for i in "${array[#]}"
do
if [ "$i" == "$yourValue" ] ; then
echo "Found"
fi
done
$ myarray=(one two three)
$ case "${myarray[#]}" in *"two"*) echo "found" ;; esac
found
I typically just use:
inarray=$(echo ${haystack[#]} | grep -o "needle" | wc -w)
non zero value indicates a match was found.
... actually, to solve the problem mentioned with it not working with needle1 and needle2, if you only want an exact match, nothing more, nothing less, just add a w flag after the -o for a whole word match:
inarray=$(echo ${haystack[#]} | grep -ow "needle" | wc -w)
If you need performance, you don't want to loop over your whole array every time you search.
In this case, you can create an associative array (hash table, or dictionary) that represents an index of that array. I.e. it maps each array element into its index in the array:
make_index () {
local index_name=$1
shift
local -a value_array=("$#")
local i
# -A means associative array, -g means create a global variable:
declare -g -A ${index_name}
for i in "${!value_array[#]}"; do
eval ${index_name}["${value_array[$i]}"]=$i
done
}
Then you can use it like this:
myarray=('a a' 'b b' 'c c')
make_index myarray_index "${myarray[#]}"
And test membership like so:
member="b b"
# the "|| echo NOT FOUND" below is needed if you're using "set -e"
test "${myarray_index[$member]}" && echo FOUND || echo NOT FOUND
Or also:
if [ "${myarray_index[$member]}" ]; then
echo FOUND
fi
Notice that this solution does the right thing even if the there are spaces in the tested value or in the array values.
As a bonus, you also get the index of the value within the array with:
echo "<< ${myarray_index[$member]} >> is the index of $member"
Another one liner without a function:
(for e in "${array[#]}"; do [[ "$e" == "searched_item" ]] && exit 0; done) && echo "found" || echo "not found"
Thanks #Qwerty for the heads up regarding spaces!
corresponding function:
find_in_array() {
local word=$1
shift
for e in "$#"; do [[ "$e" == "$word" ]] && return 0; done
return 1
}
example:
some_words=( these are some words )
find_in_array word "${some_words[#]}" || echo "expected missing! since words != word"
How to check if a Bash Array contains a value
False positive match
array=(a1 b1 c1 d1 ee)
[[ ${array[*]} =~ 'a' ]] && echo 'yes' || echo 'no'
# output:
yes
[[ ${array[*]} =~ 'a1' ]] && echo 'yes' || echo 'no'
# output:
yes
[[ ${array[*]} =~ 'e' ]] && echo 'yes' || echo 'no'
# output:
yes
[[ ${array[*]} =~ 'ee' ]] && echo 'yes' || echo 'no'
# output:
yes
Exact match
In order to look for an exact match, your regex pattern needs to add extra space before and after the value like (^|[[:space:]])"VALUE"($|[[:space:]])
# Exact match
array=(aa1 bc1 ac1 ed1 aee)
if [[ ${array[*]} =~ (^|[[:space:]])"a"($|[[:space:]]) ]]; then
echo "Yes";
else
echo "No";
fi
# output:
No
if [[ ${array[*]} =~ (^|[[:space:]])"ac1"($|[[:space:]]) ]]; then
echo "Yes";
else
echo "No";
fi
# output:
Yes
find="ac1"
if [[ ${array[*]} =~ (^|[[:space:]])"$find"($|[[:space:]]) ]]; then
echo "Yes";
else
echo "No";
fi
# output:
Yes
For more usage examples the source of examples are here
containsElement () { for e in "${#:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }
Now handles empty arrays correctly.
If you want to do a quick and dirty test to see if it's worth iterating over the whole array to get a precise match, Bash can treat arrays like scalars. Test for a match in the scalar, if none then skipping the loop saves time. Obviously you can get false positives.
array=(word "two words" words)
if [[ ${array[#]} =~ words ]]
then
echo "Checking"
for element in "${array[#]}"
do
if [[ $element == "words" ]]
then
echo "Match"
fi
done
fi
This will output "Checking" and "Match". With array=(word "two words" something) it will only output "Checking". With array=(word "two widgets" something) there will be no output.
Here is a small contribution :
array=(word "two words" words)
search_string="two"
match=$(echo "${array[#]:0}" | grep -o $search_string)
[[ ! -z $match ]] && echo "found !"
Note: this way doesn't distinguish the case "two words" but this is not required in the question.
Here's a compilation of several possible implementations, complete with integrated verification and simple benchmarking (requires Bash >= 4.0):
#!/usr/bin/env bash
# Check if array contains item [$1: item, $2: array name]
function in_array_1() {
local needle="$1" item
local -n arrref="$2"
for item in "${arrref[#]}"; do
[[ "${item}" == "${needle}" ]] && return 0
done
return 1
}
# Check if array contains item [$1: item, $2: array name]
function in_array_2() {
local needle="$1" arrref="$2[#]" item
for item in "${!arrref}"; do
[[ "${item}" == "${needle}" ]] && return 0
done
return 1
}
# Check if array contains item [$1: item, $2: array name]
function in_array_3() {
local needle="$1" i
local -n arrref="$2"
for ((i=0; i < ${#arrref[#]}; i++)); do
[[ "${arrref[i]}" == "${needle}" ]] && return 0
done
return 1
}
# Check if array contains item [$1: item, $2..$n: array items]
function in_array_4() {
local needle="$1" item
shift
for item; do
[[ "${item}" == "${needle}" ]] && return 0
done
return 1
}
# Check if array contains item [$1: item, $2..$n: array items]
function in_array_5() {
local needle="$1" item
for item in "${#:2}"; do
[[ "${item}" == "${needle}" ]] && return 0
done
return 1
}
# Check if array contains item [$1: item, $2: array name]
function in_array_6() {
local needle="$1" arrref="$2[#]" array i
array=("${!arrref}")
for ((i=0; i < ${#array[#]}; i++)); do
[[ "${array[i]}" == "${needle}" ]] && return 0
done
return 1
}
# Check if array contains item [$1: item, $2..$n: array items]
function in_array_7() {
local needle="$1" array=("${#:2}") item
for item in "${array[#]}"; do
[[ "${item}" == "${needle}" ]] && return 0
done
return 1
}
# Check if array contains item [$1: item, $2..$n: array items]
function in_array_8() {
local needle="$1"
shift
while (( $# > 0 )); do
[[ "$1" == "${needle}" ]] && return 0
shift
done
return 1
}
#------------------------------------------------------------------------------
# Generate map for array [$1: name of source array, $2: name of target array]
# NOTE: target array must be pre-declared by caller using 'declare -A <name>'
function generate_array_map() {
local -n srcarr="$1" dstmap="$2"
local i key
dstmap=()
for i in "${!srcarr[#]}"; do
key="${srcarr[i]}"
[[ -z ${dstmap["${key}"]+set} ]] && dstmap["${key}"]=${i} || dstmap["${key}"]+=,${i}
done
}
# Check if array contains item [$1: item, $2: name of array map]
function in_array_9() {
local needle="$1"
local -n mapref="$2"
[[ -n "${mapref["${needle}"]+set}" ]] && return 0 || return 1
}
#------------------------------------------------------------------------------
# Test in_array function [$1: function name, $2: function description, $3: test array size]
function test() {
local tname="$1" tdesc="$2" tn=$3 ti=0 tj=0 ta=() tct=0 tepapre="" tepapost="" tepadiff=()
local -A tam=()
echo -e "\e[1m${tname} (${tdesc}):\e[0m"
# Generate list of currently defined variables
tepapre="$(compgen -v)"
# Fill array with random items
for ((ti=0; ti < ${tn}; ti++)); do
ta+=("${RANDOM} ${RANDOM} ${RANDOM} ${RANDOM}")
done
# Determine function call type (pass array items, pass array name, pass array map)
case "${tname}" in
"in_array_1"|"in_array_2"|"in_array_3"|"in_array_6") tct=0; ;;
"in_array_4"|"in_array_5"|"in_array_7"|"in_array_8") tct=1; ;;
"in_array_9") generate_array_map ta tam; tct=2; ;;
*) echo "Unknown in_array function '${tname}', aborting"; return 1; ;;
esac
# Verify in_array function is working as expected by picking a few random
# items and checking
echo -e "\e[1mVerification...\e[0m"
for ((ti=0; ti < 10; ti++)); do
tj=$(( ${RANDOM} % ${#ta[#]} ))
echo -n "Item ${tj} '${ta[tj]}': "
if (( ${tct} == 0 )); then
"${tname}" "${ta[tj]}" ta && echo -en "\e[1;32mok\e[0m" || echo -en "\e[1;31mnok\e[0m"
echo -n " "
"${tname}" "${ta[tj]}.x" ta && echo -en "\e[1;31mnok\e[0m" || echo -en "\e[1;32mok\e[0m"
elif (( ${tct} == 1 )); then
"${tname}" "${ta[tj]}" "${ta[#]}" && echo -en "\e[1;32mok\e[0m" || echo -en "\e[1;31mnok\e[0m"
echo -n " "
"${tname}" "${ta[tj]}.x" "${ta[#]}" && echo -en "\e[1;31mnok\e[0m" || echo -en "\e[1;32mok\e[0m"
elif (( ${tct} == 2 )); then
"${tname}" "${ta[tj]}" tam && echo -en "\e[1;32mok\e[0m" || echo -en "\e[1;31mnok\e[0m"
echo -n " "
"${tname}" "${ta[tj]}.x" tam && echo -en "\e[1;31mnok\e[0m" || echo -en "\e[1;32mok\e[0m"
fi
echo
done
# Benchmark in_array function
echo -en "\e[1mBenchmark...\e[0m"
time for ((ti=0; ti < ${#ta[#]}; ti++)); do
if (( ${tct} == 0 )); then
"${tname}" "${ta[ti]}" ta
elif (( ${tct} == 1 )); then
"${tname}" "${ta[ti]}" "${ta[#]}"
elif (( ${tct} == 2 )); then
"${tname}" "${ta[ti]}" tam
fi
done
# Generate list of currently defined variables, compare to previously
# generated list to determine possible environment pollution
echo -e "\e[1mEPA test...\e[0m"
tepapost="$(compgen -v)"
readarray -t tepadiff < <(echo -e "${tepapre}\n${tepapost}" | sort | uniq -u)
if (( ${#tepadiff[#]} == 0 )); then
echo -e "\e[1;32mclean\e[0m"
else
echo -e "\e[1;31mpolluted:\e[0m ${tepadiff[#]}"
fi
echo
}
#------------------------------------------------------------------------------
# Test in_array functions
n=5000
echo
( test in_array_1 "pass array name, nameref reference, for-each-loop over array items" ${n} )
( test in_array_2 "pass array name, indirect reference, for-each-loop over array items" ${n} )
( test in_array_3 "pass array name, nameref reference, c-style for-loop over array items by index" ${n} )
( test in_array_4 "pass array items, for-each-loop over arguments" ${n} )
( test in_array_5 "pass array items, for-each-loop over arguments as array" ${n} )
( test in_array_6 "pass array name, indirect reference + array copy, c-style for-loop over array items by index" ${n} )
( test in_array_7 "pass array items, copy array from arguments as array, for-each-loop over array items" ${n} )
( test in_array_8 "pass array items, while-loop, shift over arguments" ${n} )
( test in_array_9 "pre-generated array map, pass array map name, direct test without loop" ${n} )
Results:
in_array_1 (pass array name, nameref reference, for-each-loop over array items):
Verification...
Item 862 '19528 10140 12669 17820': ok ok
Item 2250 '27262 30442 9295 24867': ok ok
Item 4794 '3857 17404 31925 27993': ok ok
Item 2532 '14553 12282 26511 32657': ok ok
Item 1911 '21715 8066 15277 27126': ok ok
Item 4289 '3081 10265 16686 19121': ok ok
Item 4837 '32220 1758 304 7871': ok ok
Item 901 '20652 23880 20634 14286': ok ok
Item 2488 '14578 8625 30251 9343': ok ok
Item 4165 '4514 25064 29301 7400': ok ok
Benchmark...
real 1m11,796s
user 1m11,262s
sys 0m0,473s
EPA test...
clean
in_array_2 (pass array name, indirect reference, for-each-loop over array items):
Verification...
Item 2933 '17482 25789 27710 2096': ok ok
Item 3584 '876 14586 20885 8567': ok ok
Item 872 '176 19749 27265 18038': ok ok
Item 595 '6597 31710 13266 8813': ok ok
Item 748 '569 9200 28914 11297': ok ok
Item 3791 '26477 13218 30172 31532': ok ok
Item 2900 '3059 8457 4879 16634': ok ok
Item 676 '23511 686 589 7265': ok ok
Item 2248 '31351 7961 17946 24782': ok ok
Item 511 '8484 23162 11050 426': ok ok
Benchmark...
real 1m11,524s
user 1m11,086s
sys 0m0,437s
EPA test...
clean
in_array_3 (pass array name, nameref reference, c-style for-loop over array items by index):
Verification...
Item 1589 '747 10250 20133 29230': ok ok
Item 488 '12827 18892 31996 1977': ok ok
Item 801 '19439 25243 24485 24435': ok ok
Item 2588 '17193 18893 21610 9302': ok ok
Item 4436 '7100 655 8847 3068': ok ok
Item 2620 '19444 6457 28835 24717': ok ok
Item 4398 '4420 16336 612 4255': ok ok
Item 2430 '32397 2402 12631 29774': ok ok
Item 3419 '906 5361 32752 7698': ok ok
Item 356 '9776 16485 20838 13330': ok ok
Benchmark...
real 1m17,037s
user 1m17,019s
sys 0m0,005s
EPA test...
clean
in_array_4 (pass array items, for-each-loop over arguments):
Verification...
Item 1388 '7932 15114 4025 15625': ok ok
Item 3900 '23863 25328 5632 2752': ok ok
Item 2678 '31296 4216 17485 8874': ok ok
Item 1893 '16952 29047 29104 23384': ok ok
Item 1616 '19543 5999 4485 22929': ok ok
Item 93 '14456 2806 12829 19552': ok ok
Item 265 '30961 19733 11863 3101': ok ok
Item 4615 '10431 9566 25767 13518': ok ok
Item 576 '11726 15104 11116 74': ok ok
Item 3829 '19371 25026 6252 29478': ok ok
Benchmark...
real 1m30,912s
user 1m30,740s
sys 0m0,011s
EPA test...
clean
in_array_5 (pass array items, for-each-loop over arguments as array):
Verification...
Item 1012 '29213 31971 21483 30225': ok ok
Item 2802 '4079 5423 29240 29619': ok ok
Item 473 '6968 798 23936 6852': ok ok
Item 2183 '20734 4521 30800 2126': ok ok
Item 3059 '14952 9918 15695 19309': ok ok
Item 1424 '25784 28380 14555 21893': ok ok
Item 1087 '16345 19823 26210 20083': ok ok
Item 257 '28890 5198 7251 3866': ok ok
Item 3986 '29035 19288 12107 3857': ok ok
Item 2509 '9219 32484 12842 27472': ok ok
Benchmark...
real 1m53,485s
user 1m53,404s
sys 0m0,077s
EPA test...
clean
in_array_6 (pass array name, indirect reference + array copy, c-style for-loop over array items by index):
Verification...
Item 4691 '25498 10521 20673 14948': ok ok
Item 263 '25265 29824 3876 14088': ok ok
Item 2550 '2416 14274 12594 29740': ok ok
Item 2269 '2769 11436 3622 28273': ok ok
Item 3246 '23730 25956 3514 17626': ok ok
Item 1059 '10776 12514 27222 15640': ok ok
Item 53 '23813 13365 16022 4092': ok ok
Item 1503 '6593 23540 10256 17818': ok ok
Item 2452 '12600 27404 30960 26759': ok ok
Item 2526 '21190 32512 23651 7865': ok ok
Benchmark...
real 1m54,793s
user 1m54,326s
sys 0m0,457s
EPA test...
clean
in_array_7 (pass array items, copy array from arguments as array, for-each-loop over array items):
Verification...
Item 2212 '12127 12828 27570 7051': ok ok
Item 1393 '19552 26263 1067 23332': ok ok
Item 506 '18818 8253 14924 30710': ok ok
Item 789 '9803 1886 17584 32686': ok ok
Item 1795 '19788 27842 28044 3436': ok ok
Item 376 '4372 16953 17280 4031': ok ok
Item 4846 '19130 6261 21959 6869': ok ok
Item 2064 '2357 32221 22682 5814': ok ok
Item 4866 '10928 10632 19175 14984': ok ok
Item 1294 '8499 11885 5900 6765': ok ok
Benchmark...
real 2m35,012s
user 2m33,578s
sys 0m1,433s
EPA test...
clean
in_array_8 (pass array items, while-loop, shift over arguments):
Verification...
Item 134 '1418 24798 20169 9501': ok ok
Item 3986 '12160 12021 29794 29236': ok ok
Item 1607 '26633 14260 18227 898': ok ok
Item 2688 '18387 6285 2385 18432': ok ok
Item 603 '1421 306 6102 28735': ok ok
Item 625 '4530 19718 30900 1938': ok ok
Item 4033 '9968 24093 25080 8179': ok ok
Item 310 '6867 9884 31231 29173': ok ok
Item 661 '3794 4745 26066 22691': ok ok
Item 4129 '3039 31766 6714 4921': ok ok
Benchmark...
real 5m51,097s
user 5m50,566s
sys 0m0,495s
EPA test...
clean
in_array_9 (pre-generated array map, pass array map name, direct test without loop):
Verification...
Item 3696 '661 6048 13881 26901': ok ok
Item 815 '29729 13733 3935 20697': ok ok
Item 1076 '9220 3405 18448 7240': ok ok
Item 595 '8912 2886 13678 24066': ok ok
Item 2803 '13534 23891 5344 652': ok ok
Item 1810 '12528 32150 7050 1254': ok ok
Item 4055 '21840 7436 1350 15443': ok ok
Item 2416 '19550 28434 17110 31203': ok ok
Item 1630 '21054 2819 7527 953': ok ok
Item 1044 '30152 22211 22226 6950': ok ok
Benchmark...
real 0m0,128s
user 0m0,128s
sys 0m0,000s
EPA test...
clean
a=(b c d)
if printf '%s\0' "${a[#]}" | grep -Fqxz c
then
echo 'array “a” contains value “c”'
fi
If you prefer you can use equivalent long options:
--fixed-strings --quiet --line-regexp --null-data
Borrowing from Dennis Williamson's answer, the following solution combines arrays, shell-safe quoting, and regular expressions to avoid the need for: iterating over loops; using pipes or other sub-processes; or using non-bash utilities.
declare -a array=('hello, stack' one 'two words' words last)
printf -v array_str -- ',,%q' "${array[#]}"
if [[ "${array_str},," =~ ,,words,, ]]
then
echo 'Matches'
else
echo "Doesn't match"
fi
The above code works by using Bash regular expressions to match against a stringified version of the array contents. There are six important steps to ensure that the regular expression match can't be fooled by clever combinations of values within the array:
Construct the comparison string by using Bash's built-in printf shell-quoting, %q. Shell-quoting will ensure that special characters become "shell-safe" by being escaped with backslash \.
Choose a special character to serve as a value delimiter. The delimiter HAS to be one of the special characters that will become escaped when using %q; that's the only way to guarantee that values within the array can't be constructed in clever ways to fool the regular expression match. I choose comma , because that character is the safest when eval'd or misused in an otherwise unexpected way.
Combine all array elements into a single string, using two instances of the special character to serve as delimiter. Using comma as an example, I used ,,%q as the argument to printf. This is important because two instances of the special character can only appear next to each other when they appear as the delimiter; all other instances of the special character will be escaped.
Append two trailing instances of the delimiter to the string, to allow matches against the last element of the array. Thus, instead of comparing against ${array_str}, compare against ${array_str},,.
If the target string you're searching for is supplied by a user variable, you must escape all instances of the special character with a backslash. Otherwise, the regular expression match becomes vulnerable to being fooled by cleverly-crafted array elements.
Perform a Bash regular expression match against the string.
This is working for me:
# traditional system call return values-- used in an `if`, this will be true when returning 0. Very Odd.
contains () {
# odd syntax here for passing array parameters: http://stackoverflow.com/questions/8082947/how-to-pass-an-array-to-a-bash-function
local list=$1[#]
local elem=$2
# echo "list" ${!list}
# echo "elem" $elem
for i in "${!list}"
do
# echo "Checking to see if" "$i" "is the same as" "${elem}"
if [ "$i" == "${elem}" ] ; then
# echo "$i" "was the same as" "${elem}"
return 0
fi
done
# echo "Could not find element"
return 1
}
Example call:
arr=("abc" "xyz" "123")
if contains arr "abcx"; then
echo "Yes"
else
echo "No"
fi
The answer with most votes is very concise and clean, but it can have false positives when a space is part of one of the array elements. This can be overcome when changing IFS and using "${array[*]}" instead of "${array[#]}". The method is identical, but it looks less clean. By using "${array[*]}", we print all elements of $array, separated by the first character in IFS. So by choosing a correct IFS, you can overcome this particular issue. In this particular case, we decide to set IFS to an uncommon character $'\001' which stands for Start of Heading (SOH)
$ array=("foo bar" "baz" "qux")
$ IFS=$'\001'
$ [[ "$IFS${array[*]}$IFS" =~ "${IFS}foo${IFS}" ]] && echo yes || echo no
no
$ [[ "$IFS${array[*]}$IFS" =~ "${IFS}foo bar${IFS}" ]] && echo yes || echo no
yes
$ unset IFS
This resolves most issues false positives, but requires a good choice of IFS.
note: If IFS was set before, it is best to save it and reset it instead of using unset IFS
related:
Accessing bash command line args $# vs $*
One-line check without 'grep' and loops
if ( dlm=$'\x1F' ; IFS="$dlm" ; [[ "$dlm${array[*]}$dlm" == *"$dlm${item}$dlm"* ]] ) ; then
echo "array contains '$item'"
else
echo "array does not contain '$item'"
fi
This approach uses neither external utilities like grep nor loops.
What happens here, is:
we use a wildcard substring matcher to find our item in the array that is concatenated into a string;
we cut off possible false positives by enclosing our search item between a pair of delimiters;
we use a non-printable character as delimiter, to be on the safe side;
we achieve our delimiter being used for array concatenation too by temporary replacement of the IFS variable value;
we make this IFS value replacement temporary by evaluating our conditional expression in a sub-shell (inside a pair of parentheses)
Stop the madness! Make your solution simple, clean, and reusable.
These functions account for indexed arrays and associative arrays. They can be improved upon by upgrading the search algorithm from a linear search to a binary one (for large data sets).
##
# Determines if a value exists in an array.
###
function hasArrayValue ()
{
local -r needle="{$1:?}"
local -nr haystack="{$2:?}" # Where you pass by reference to get the entire array in one argument.
# Linear search. Upgrade to binary search for large datasets.
for value in "${haystack[#]}"; do
if [[ "$value" == "$needle" ]]; then
return 0
fi
done
return 1
}
##
# Determines if a value exists in an associative array / map.
###
function hasMapValue ()
{
local -r needle="{$1:?}"
local -nr haystack="{$2:?}"
# Linear search. Upgrade to binary search for large datasets.
for value in "${haystack[#]}"; do
if [[ $value == $needle ]]; then
return 0
fi
done
return 1
}
Yes, same logic, but in dealing with bash, if might (might) be useful to have a function with a name that lets you know what is being iterated over (or not).
given :
array=("something to search for" "a string" "test2000")
elem="a string"
then a simple check of :
if c=$'\x1E' && p="${c}${elem} ${c}" && [[ ! "${array[#]/#/${c}} ${c}" =~ $p ]]; then
echo "$elem exists in array"
fi
where
c is element separator
p is regex pattern
(The reason for assigning p separately, rather than using the expression directly inside [[ ]] is to maintain compatibility for bash 4)
Using grep and printf
Format each array member on a new line, then grep the lines.
if printf '%s\n' "${array[#]}" | grep -x -q "search string"; then echo true; else echo false; fi
example:
$ array=("word", "two words")
$ if printf '%s\n' "${array[#]}" | grep -x -q "two words"; then echo true; else echo false; fi
true
Note that this has no problems with delimeters and spaces.
A small addition to #ghostdog74's answer about using case logic to check that array contains particular value:
myarray=(one two three)
word=two
case "${myarray[#]}" in ("$word "*|*" $word "*|*" $word") echo "found" ;; esac
Or with extglob option turned on, you can do it like this:
myarray=(one two three)
word=two
shopt -s extglob
case "${myarray[#]}" in ?(*" ")"$word"?(" "*)) echo "found" ;; esac
Also we can do it with if statement:
myarray=(one two three)
word=two
if [[ $(printf "_[%s]_" "${myarray[#]}") =~ .*_\[$word\]_.* ]]; then echo "found"; fi
Combining a few of the ideas presented here you can make an elegant if statment without loops that does exact word matches.
find="myword"
array=(value1 value2 myword)
if [[ ! -z $(printf '%s\n' "${array[#]}" | grep -w $find) ]]; then
echo "Array contains myword";
fi
This will not trigger on word or val, only whole word matches. It will break if each array value contains multiple words.
I generally write these kind of utilities to operate on the name of the variable, rather than the variable value, primarily because bash can't otherwise pass variables by reference.
Here's a version that works with the name of the array:
function array_contains # array value
{
[[ -n "$1" && -n "$2" ]] || {
echo "usage: array_contains <array> <value>"
echo "Returns 0 if array contains value, 1 otherwise"
return 2
}
eval 'local values=("${'$1'[#]}")'
local element
for element in "${values[#]}"; do
[[ "$element" == "$2" ]] && return 0
done
return 1
}
With this, the question example becomes:
array_contains A "one" && echo "contains one"
etc.
Using parameter expansion:
${parameter:+word} If parameter is null or unset, nothing is
substituted, otherwise the expansion of word is substituted.
declare -A myarray
myarray[hello]="world"
for i in hello goodbye 123
do
if [ ${myarray[$i]:+_} ]
then
echo ${!myarray[$i]} ${myarray[$i]}
else
printf "there is no %s\n" $i
fi
done
The OP added the following answer themselves, with the commentary:
With help from the answers and the comments, after some testing, I came up with this:
function contains() {
local n=$#
local value=${!n}
for ((i=1;i < $#;i++)) {
if [ "${!i}" == "${value}" ]; then
echo "y"
return 0
fi
}
echo "n"
return 1
}
A=("one" "two" "three four")
if [ $(contains "${A[#]}" "one") == "y" ]; then
echo "contains one"
fi
if [ $(contains "${A[#]}" "three") == "y" ]; then
echo "contains three"
fi
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"
: NeedleInArgs "$needle" "${haystack[#]}"
: NeedleInArgs "$needle" arg1 arg2 .. argN
NeedleInArgs()
{
local a b;
printf -va '\n%q\n' "$1";
printf -vb '%q\n' "${#:2}";
case $'\n'"$b" in (*"$a"*) return 0;; esac;
return 1;
}
Use like:
NeedleInArgs "$needle" "${haystack[#]}" && echo "$needle" found || echo "$needle" not found;
For bash v3.1 and above (printf -v support)
No forks nor external programs
No loops (except internal expansions within bash)
Works for all possible values and arrays, no exceptions, nothing to worry about
Can also be used directly like in:
if NeedleInArgs "$input" value1 value2 value3 value4;
then
: input from the list;
else
: input not from list;
fi;
For bash from v2.05b to v3.0 printf lacks -v, hence this needs 2 additional forks (but no execs, as printf is a bash builtin):
NeedleInArgs()
{
case $'\n'"`printf '%q\n' "${#:2}"`" in
(*"`printf '\n%q\n' "$1"`"*) return 0;;
esac;
return 1;
}
Note that I tested the timing:
check call0: n: t4.43 u4.41 s0.00 f: t3.65 u3.64 s0.00 l: t4.91 u4.90 s0.00 N: t5.28 u5.27 s0.00 F: t2.38 u2.38 s0.00 L: t5.20 u5.20 s0.00
check call1: n: t3.41 u3.40 s0.00 f: t2.86 u2.84 s0.01 l: t3.72 u3.69 s0.02 N: t4.01 u4.00 s0.00 F: t1.15 u1.15 s0.00 L: t4.05 u4.05 s0.00
check call2: n: t3.52 u3.50 s0.01 f: t3.74 u3.73 s0.00 l: t3.82 u3.80 s0.01 N: t2.67 u2.67 s0.00 F: t2.64 u2.64 s0.00 L: t2.68 u2.68 s0.00
call0 and call1 are different variants of calls to another fast pure-bash-variant
call2 is this here.
N=notfound F=firstmatch L=lastmatch
lowercase letter is short array, uppercase is long array
As you can see, this variant here has a very stable runtime, so it does not depend that much on the match position. The runtime is dominated mostly by array length. The runtime of the searching variant is highly depending on the match position. So in edge cases this variant here can be (much) faster.
But very important, the searching variant is much mor RAM efficient, as this variant here always transforms the whole array into a big string.
So if your RAM is tight and you expect mostly early matches, then do not use this here. However if you want a predictable runtime, have long arrays to match expect late or no match at all, and also double RAM use is not much of a concern, then this here has some advantage.
Script used for timing test:
in_array()
{
local needle="$1" arrref="$2[#]" item
for item in "${!arrref}"; do
[[ "${item}" == "${needle}" ]] && return 0
done
return 1
}
NeedleInArgs()
{
local a b;
printf -va '\n%q\n' "$1";
printf -vb '%q\n' "${#:2}";
case $'\n'"$b" in (*"$a"*) return 0;; esac;
return 1;
}
loop1() { for a in {1..100000}; do "$#"; done }
loop2() { for a in {1..1000}; do "$#"; done }
run()
{
needle="$5"
arr=("${#:6}")
out="$( ( time -p "loop$2" "$3" ) 2>&1 )"
ret="$?"
got="${out}"
syst="${got##*sys }"
got="${got%"sys $syst"}"
got="${got%$'\n'}"
user="${got##*user }"
got="${got%"user $user"}"
got="${got%$'\n'}"
real="${got##*real }"
got="${got%"real $real"}"
got="${got%$'\n'}"
printf ' %s: t%q u%q s%q' "$1" "$real" "$user" "$syst"
[ -z "$rest" ] && [ "$ret" = "$4" ] && return
printf 'FAIL! expected %q got %q\n' "$4" "$ret"
printf 'call: %q\n' "$3"
printf 'out: %q\n' "$out"
printf 'rest: %q\n' "$rest"
printf 'needle: %q\n' "$5"
printf 'arr: '; printf ' %q' "${#:6}"; printf '\n'
exit 1
}
check()
{
printf 'check %q: ' "$1"
run n 1 "$1" 1 needle a b c d
run f 1 "$1" 0 needle needle a b c d
run l 1 "$1" 0 needle a b c d needle
run N 2 "$1" 1 needle "${rnd[#]}"
run F 2 "$1" 0 needle needle "${rnd[#]}"
run L 2 "$1" 0 needle "${rnd[#]}" needle
printf '\n'
}
call0() { chk=("${arr[#]}"); in_array "$needle" chk; }
call1() { in_array "$needle" arr; }
call2() { NeedleInArgs "$needle" "${arr[#]}"; }
rnd=()
for a in {1..1000}; do rnd+=("$a"); done
check call0
check call1
check call2
I see a few ways to approach the problem.
For index array using grep
grep ${value} <<< ${array[*]} && true || false
For associative array keys using grep
grep ${value} <<< "${!array[*]}" && true || false
One could use awk, but it's probably overkill.
awk --assign "v=${value}" '$v~$0 {print true}' <<<"${!array[*]}
Case statement.
case "${array[*]}" in (*${value}*) true ;; (*) false ;; esac
Bash conditional expressions in ksh88 style double square bracket:
[[ ${array[#]} =~ ${value} ]] && true || false
note: the order is important, the regex is on the right side of =~ match operator.
Bash for loop
for ((i=0;i<"${#array[*]}";i++)) ; [[ ${array[i]} = $value ]] && break 0 &> /dev/null || continue; done
Note, in this special case the truthy logic is inversed, I.E. 1=true, 0=false. That is because we use break 0 to force break builtin to have an exit code besides true, which is always the case unless the break n parameter is less than 1. We imperatively want to break the loop, and we want a boolean exit code besides default 'true', so in this case we flip the logic. For that reason it would probably make more sense to use a function with return true semantics.
After having answered, I read another answer that I particularly liked, but it was flawed and downvoted. I got inspired and here are two new approaches I see viable.
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>

how to capture only digits in array using if statement in bash

len=${#newarray[*]}
for ((i=0;i<${len};i++)); do
if [[ "${newarray[$i]}" =~ ^[[:digit:]]+$ ]]; then
echo "${newarray[$i]}"
fi
this is my code I need to get only digits from the array list and their count as well. but there is an issue that shows alphabetical values only.
given getdata.txtcontains -
cat
dog
1234
pl345
567ab
12234
Having:
value=(cat dog 1234 pl345 567ab 12234)
You could print "only numbers":
$ printf "%s\n" "${value[#]}" | grep -o "[0-9]*"
1234
345
567
12234
or maybe you want "only lines that contain only numbers":
$ printf "%s\n" "${value[#]}" | grep -x "[0-9]*"
1234
12234
or maybe you want "only lines with a number":
$ printf "%s\n" "${value[#]}" | grep "[0-9]"
1234
pl345
567ab
12234
Your if/else is backwards.
value=(cat dog 1234 pl345 567ab 12234)
len=${#value[*]}
for ((i=0;i<${len};i++)); do
if [[ "${value[$i]}" =~ ^[[:digit:]]+$ ]]; then
echo "${value[$i]}"
fi
done
This outputs:
1234
12234
There is a non-regex way of doing it in bash using shopt:
# enable extended glob
shopt -s extglob
value=(cat dog 1234 pl345 567ab 12234)
# lop through array and print only it contains 1+ digits
for v in "${value[#]}"; do [[ $v == +([0-9]) ]] && echo "$v"; done
1234
12234
You probably should use two nested loops:
iterate over all elements of an array
iterate over all chars in the element, check if the char is a digit
(2) would probably be simpler with grep, but since we're speaking of bash implementaton I've came up with this:
#!/bin/bash
a=(cat dog 1234 pl345 567ab 12234) # input array
for s in "${a[#]}"; do # iterate over all elements of our array
res=''
for ((i=0;i<${#s};i++)); do # iterate over all chars of element $s
c="${s:$i:1}" # $c is now char at $i position inside $s
case "$c" in
[0-9]) res="${res}${c}" # append digit to res
esac
done
[ -n "$res" ] && echo "$res" # if $s contains at least 1 digit, print it
# you may add $res to another array here if needed
done
Simply
tr -dc '0-9\n' <getdata.txt
You can use:
$ cat getdata.txt |grep -P '^[0-9]+$'

"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

What is the fastest way to delete an element of an array?

Giving this array:
arr=(hello asd asd1 asd22 asd333)
I want to delete the a especific item by its value, for example asd. I did this:
IFS=' '
echo "${arr[#]/asd/}"
But it returns the following:
hello 1 22 333
So I did this function:
function remove_item() {
local item_search="$1"
shift
local arr_tmp=("${#}")
if [ ${#arr_tmp[#]} -eq 0 ]; then
return
fi
local index=0
for item in ${arr_tmp[#]}; do
if [ "$item" = "$item_search" ]; then
unset arr_tmp[$index]
break
fi
let index++
done
echo "${arr_tmp[*]}"
}
arr=(asd asd1 asd22 asd333)
remove_item 'asd' "${arr[#]}"
Prints the desired output:
hello asd1 asd22 asd333
But I have to use it with very long arrays, and I have to call it a lot of times. And its performance sucks.
Do you have any better alternative to do it? Any tip, trick, or advice will be appreciatted.
You could use a loop to iterate over the array and remove the element that matches the specified value:
for i in "${!arr[#]}"; do
[[ "${arr[i]}" == "asd" ]] && unset arr[i]
done
If you know that the array would have at most one matching element, you could even break out of the loop:
[[ "${arr[i]}" == "asd" ]] && unset arr[i] && break
|^^^^^^^^|
(this causes the loop to break
as soon as the element is found)
As an example:
$ arr=(asd asd1 asd22 asd333)
$ for i in "${!arr[#]}"; do [[ "${arr[i]}" == "asd" ]] && unset arr[i]; done
$ echo "${arr[#]}"
asd1 asd22 asd333
Probably #devnull's answer is fastest. But it might possibly be faster not to use a loop and instead let grep do the work. Its not very pretty though:
$ arr=(hello asd asd1 asd22 asd333)
$ remove="asd"
$ i=$(paste -d: <(printf "%s\n" "${!arr[#]}") <(printf "%s\n" "${arr[#]}") | grep -m1 -w -E "^[[:digit:]]+:${remove}$")
$ unset arr[${i%:*}]
$

Check if a Bash array contains a value

In Bash, what is the simplest way to test if an array contains a certain value?
This approach has the advantage of not needing to loop over all the elements (at least not explicitly). But since array_to_string_internal() in array.c still loops over array elements and concatenates them into a string, it's probably not more efficient than the looping solutions proposed, but it's more readable.
if [[ " ${array[*]} " =~ " ${value} " ]]; then
# whatever you want to do when array contains value
fi
if [[ ! " ${array[*]} " =~ " ${value} " ]]; then
# whatever you want to do when array doesn't contain value
fi
Note that in cases where the value you are searching for is one of the words in an array element with spaces, it will give false positives. For example
array=("Jack Brown")
value="Jack"
The regex will see "Jack" as being in the array even though it isn't. So you'll have to change IFS and the separator characters on your regex if you want still to use this solution, like this
IFS="|"
array=("Jack Brown${IFS}Jack Smith")
value="Jack"
if [[ "${IFS}${array[*]}${IFS}" =~ "${IFS}${value}${IFS}" ]]; then
echo "true"
else
echo "false"
fi
unset IFS # or set back to original IFS if previously set
This will print "false".
Obviously this can also be used as a test statement, allowing it to be expressed as a one-liner
[[ " ${array[*]} " =~ " ${value} " ]] && echo "true" || echo "false"
Below is a small function for achieving this. The search string is the first argument and the rest are the array elements:
set +e #otherwise the script will exit on error
containsElement () {
local e match="$1"
shift
for e; do [[ "$e" == "$match" ]] && return 0; done
return 1
}
A test run of that function could look like:
$ array=("something to search for" "a string" "test2000")
$ containsElement "a string" "${array[#]}"
$ echo $?
0
$ containsElement "blaha" "${array[#]}"
$ echo $?
1
One-line solution
printf '%s\0' "${myarray[#]}" | grep -F -x -z -- 'myvalue'
Explanation
The printf statement prints each element of the array, delimited by null characters.
The grep statement uses the following flags to match an item that contains exactly the string given as myvalue (no more, no less):
-z/--null-data - Lines are terminated by a zero byte instead of a newline.
-F/--fixed-strings - Interpret PATTERNS as fixed strings, not regular expressions.
-x/--line-regexp - Select only those matches that exactly match the whole line.
-- - marks the end of command-line options, making Grep process "myvalue" as a non-option argument even if it starts with a dash
Why do we use a null byte \0 instead of a newline \n? Your array may actually contain newlines within its elements. (If you know that it doesn't, feel free to drop the -z grep option and substitude %s\n as your first printf arg.)
Usage
To put this into an if ... then statement:
if printf '%s\0' "${myarray[#]}" | grep -Fxqz -- 'myvalue'; then
# ...
fi
I added a -q flag to the grep expression so that it won't print matches; it will just treat the existence of a match as "true."
Update: Thanks, presto8, for pointing out the --line-regexp flag. Thanks, Tino, for pointing out the case where newlines can exist within array items.
for i in "${array[#]}"
do
if [ "$i" -eq "$yourValue" ] ; then
echo "Found"
fi
done
For strings:
for i in "${array[#]}"
do
if [ "$i" == "$yourValue" ] ; then
echo "Found"
fi
done
$ myarray=(one two three)
$ case "${myarray[#]}" in *"two"*) echo "found" ;; esac
found
I typically just use:
inarray=$(echo ${haystack[#]} | grep -o "needle" | wc -w)
non zero value indicates a match was found.
... actually, to solve the problem mentioned with it not working with needle1 and needle2, if you only want an exact match, nothing more, nothing less, just add a w flag after the -o for a whole word match:
inarray=$(echo ${haystack[#]} | grep -ow "needle" | wc -w)
If you need performance, you don't want to loop over your whole array every time you search.
In this case, you can create an associative array (hash table, or dictionary) that represents an index of that array. I.e. it maps each array element into its index in the array:
make_index () {
local index_name=$1
shift
local -a value_array=("$#")
local i
# -A means associative array, -g means create a global variable:
declare -g -A ${index_name}
for i in "${!value_array[#]}"; do
eval ${index_name}["${value_array[$i]}"]=$i
done
}
Then you can use it like this:
myarray=('a a' 'b b' 'c c')
make_index myarray_index "${myarray[#]}"
And test membership like so:
member="b b"
# the "|| echo NOT FOUND" below is needed if you're using "set -e"
test "${myarray_index[$member]}" && echo FOUND || echo NOT FOUND
Or also:
if [ "${myarray_index[$member]}" ]; then
echo FOUND
fi
Notice that this solution does the right thing even if the there are spaces in the tested value or in the array values.
As a bonus, you also get the index of the value within the array with:
echo "<< ${myarray_index[$member]} >> is the index of $member"
Another one liner without a function:
(for e in "${array[#]}"; do [[ "$e" == "searched_item" ]] && exit 0; done) && echo "found" || echo "not found"
Thanks #Qwerty for the heads up regarding spaces!
corresponding function:
find_in_array() {
local word=$1
shift
for e in "$#"; do [[ "$e" == "$word" ]] && return 0; done
return 1
}
example:
some_words=( these are some words )
find_in_array word "${some_words[#]}" || echo "expected missing! since words != word"
How to check if a Bash Array contains a value
False positive match
array=(a1 b1 c1 d1 ee)
[[ ${array[*]} =~ 'a' ]] && echo 'yes' || echo 'no'
# output:
yes
[[ ${array[*]} =~ 'a1' ]] && echo 'yes' || echo 'no'
# output:
yes
[[ ${array[*]} =~ 'e' ]] && echo 'yes' || echo 'no'
# output:
yes
[[ ${array[*]} =~ 'ee' ]] && echo 'yes' || echo 'no'
# output:
yes
Exact match
In order to look for an exact match, your regex pattern needs to add extra space before and after the value like (^|[[:space:]])"VALUE"($|[[:space:]])
# Exact match
array=(aa1 bc1 ac1 ed1 aee)
if [[ ${array[*]} =~ (^|[[:space:]])"a"($|[[:space:]]) ]]; then
echo "Yes";
else
echo "No";
fi
# output:
No
if [[ ${array[*]} =~ (^|[[:space:]])"ac1"($|[[:space:]]) ]]; then
echo "Yes";
else
echo "No";
fi
# output:
Yes
find="ac1"
if [[ ${array[*]} =~ (^|[[:space:]])"$find"($|[[:space:]]) ]]; then
echo "Yes";
else
echo "No";
fi
# output:
Yes
For more usage examples the source of examples are here
containsElement () { for e in "${#:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }
Now handles empty arrays correctly.
If you want to do a quick and dirty test to see if it's worth iterating over the whole array to get a precise match, Bash can treat arrays like scalars. Test for a match in the scalar, if none then skipping the loop saves time. Obviously you can get false positives.
array=(word "two words" words)
if [[ ${array[#]} =~ words ]]
then
echo "Checking"
for element in "${array[#]}"
do
if [[ $element == "words" ]]
then
echo "Match"
fi
done
fi
This will output "Checking" and "Match". With array=(word "two words" something) it will only output "Checking". With array=(word "two widgets" something) there will be no output.
Here is a small contribution :
array=(word "two words" words)
search_string="two"
match=$(echo "${array[#]:0}" | grep -o $search_string)
[[ ! -z $match ]] && echo "found !"
Note: this way doesn't distinguish the case "two words" but this is not required in the question.
Here's a compilation of several possible implementations, complete with integrated verification and simple benchmarking (requires Bash >= 4.0):
#!/usr/bin/env bash
# Check if array contains item [$1: item, $2: array name]
function in_array_1() {
local needle="$1" item
local -n arrref="$2"
for item in "${arrref[#]}"; do
[[ "${item}" == "${needle}" ]] && return 0
done
return 1
}
# Check if array contains item [$1: item, $2: array name]
function in_array_2() {
local needle="$1" arrref="$2[#]" item
for item in "${!arrref}"; do
[[ "${item}" == "${needle}" ]] && return 0
done
return 1
}
# Check if array contains item [$1: item, $2: array name]
function in_array_3() {
local needle="$1" i
local -n arrref="$2"
for ((i=0; i < ${#arrref[#]}; i++)); do
[[ "${arrref[i]}" == "${needle}" ]] && return 0
done
return 1
}
# Check if array contains item [$1: item, $2..$n: array items]
function in_array_4() {
local needle="$1" item
shift
for item; do
[[ "${item}" == "${needle}" ]] && return 0
done
return 1
}
# Check if array contains item [$1: item, $2..$n: array items]
function in_array_5() {
local needle="$1" item
for item in "${#:2}"; do
[[ "${item}" == "${needle}" ]] && return 0
done
return 1
}
# Check if array contains item [$1: item, $2: array name]
function in_array_6() {
local needle="$1" arrref="$2[#]" array i
array=("${!arrref}")
for ((i=0; i < ${#array[#]}; i++)); do
[[ "${array[i]}" == "${needle}" ]] && return 0
done
return 1
}
# Check if array contains item [$1: item, $2..$n: array items]
function in_array_7() {
local needle="$1" array=("${#:2}") item
for item in "${array[#]}"; do
[[ "${item}" == "${needle}" ]] && return 0
done
return 1
}
# Check if array contains item [$1: item, $2..$n: array items]
function in_array_8() {
local needle="$1"
shift
while (( $# > 0 )); do
[[ "$1" == "${needle}" ]] && return 0
shift
done
return 1
}
#------------------------------------------------------------------------------
# Generate map for array [$1: name of source array, $2: name of target array]
# NOTE: target array must be pre-declared by caller using 'declare -A <name>'
function generate_array_map() {
local -n srcarr="$1" dstmap="$2"
local i key
dstmap=()
for i in "${!srcarr[#]}"; do
key="${srcarr[i]}"
[[ -z ${dstmap["${key}"]+set} ]] && dstmap["${key}"]=${i} || dstmap["${key}"]+=,${i}
done
}
# Check if array contains item [$1: item, $2: name of array map]
function in_array_9() {
local needle="$1"
local -n mapref="$2"
[[ -n "${mapref["${needle}"]+set}" ]] && return 0 || return 1
}
#------------------------------------------------------------------------------
# Test in_array function [$1: function name, $2: function description, $3: test array size]
function test() {
local tname="$1" tdesc="$2" tn=$3 ti=0 tj=0 ta=() tct=0 tepapre="" tepapost="" tepadiff=()
local -A tam=()
echo -e "\e[1m${tname} (${tdesc}):\e[0m"
# Generate list of currently defined variables
tepapre="$(compgen -v)"
# Fill array with random items
for ((ti=0; ti < ${tn}; ti++)); do
ta+=("${RANDOM} ${RANDOM} ${RANDOM} ${RANDOM}")
done
# Determine function call type (pass array items, pass array name, pass array map)
case "${tname}" in
"in_array_1"|"in_array_2"|"in_array_3"|"in_array_6") tct=0; ;;
"in_array_4"|"in_array_5"|"in_array_7"|"in_array_8") tct=1; ;;
"in_array_9") generate_array_map ta tam; tct=2; ;;
*) echo "Unknown in_array function '${tname}', aborting"; return 1; ;;
esac
# Verify in_array function is working as expected by picking a few random
# items and checking
echo -e "\e[1mVerification...\e[0m"
for ((ti=0; ti < 10; ti++)); do
tj=$(( ${RANDOM} % ${#ta[#]} ))
echo -n "Item ${tj} '${ta[tj]}': "
if (( ${tct} == 0 )); then
"${tname}" "${ta[tj]}" ta && echo -en "\e[1;32mok\e[0m" || echo -en "\e[1;31mnok\e[0m"
echo -n " "
"${tname}" "${ta[tj]}.x" ta && echo -en "\e[1;31mnok\e[0m" || echo -en "\e[1;32mok\e[0m"
elif (( ${tct} == 1 )); then
"${tname}" "${ta[tj]}" "${ta[#]}" && echo -en "\e[1;32mok\e[0m" || echo -en "\e[1;31mnok\e[0m"
echo -n " "
"${tname}" "${ta[tj]}.x" "${ta[#]}" && echo -en "\e[1;31mnok\e[0m" || echo -en "\e[1;32mok\e[0m"
elif (( ${tct} == 2 )); then
"${tname}" "${ta[tj]}" tam && echo -en "\e[1;32mok\e[0m" || echo -en "\e[1;31mnok\e[0m"
echo -n " "
"${tname}" "${ta[tj]}.x" tam && echo -en "\e[1;31mnok\e[0m" || echo -en "\e[1;32mok\e[0m"
fi
echo
done
# Benchmark in_array function
echo -en "\e[1mBenchmark...\e[0m"
time for ((ti=0; ti < ${#ta[#]}; ti++)); do
if (( ${tct} == 0 )); then
"${tname}" "${ta[ti]}" ta
elif (( ${tct} == 1 )); then
"${tname}" "${ta[ti]}" "${ta[#]}"
elif (( ${tct} == 2 )); then
"${tname}" "${ta[ti]}" tam
fi
done
# Generate list of currently defined variables, compare to previously
# generated list to determine possible environment pollution
echo -e "\e[1mEPA test...\e[0m"
tepapost="$(compgen -v)"
readarray -t tepadiff < <(echo -e "${tepapre}\n${tepapost}" | sort | uniq -u)
if (( ${#tepadiff[#]} == 0 )); then
echo -e "\e[1;32mclean\e[0m"
else
echo -e "\e[1;31mpolluted:\e[0m ${tepadiff[#]}"
fi
echo
}
#------------------------------------------------------------------------------
# Test in_array functions
n=5000
echo
( test in_array_1 "pass array name, nameref reference, for-each-loop over array items" ${n} )
( test in_array_2 "pass array name, indirect reference, for-each-loop over array items" ${n} )
( test in_array_3 "pass array name, nameref reference, c-style for-loop over array items by index" ${n} )
( test in_array_4 "pass array items, for-each-loop over arguments" ${n} )
( test in_array_5 "pass array items, for-each-loop over arguments as array" ${n} )
( test in_array_6 "pass array name, indirect reference + array copy, c-style for-loop over array items by index" ${n} )
( test in_array_7 "pass array items, copy array from arguments as array, for-each-loop over array items" ${n} )
( test in_array_8 "pass array items, while-loop, shift over arguments" ${n} )
( test in_array_9 "pre-generated array map, pass array map name, direct test without loop" ${n} )
Results:
in_array_1 (pass array name, nameref reference, for-each-loop over array items):
Verification...
Item 862 '19528 10140 12669 17820': ok ok
Item 2250 '27262 30442 9295 24867': ok ok
Item 4794 '3857 17404 31925 27993': ok ok
Item 2532 '14553 12282 26511 32657': ok ok
Item 1911 '21715 8066 15277 27126': ok ok
Item 4289 '3081 10265 16686 19121': ok ok
Item 4837 '32220 1758 304 7871': ok ok
Item 901 '20652 23880 20634 14286': ok ok
Item 2488 '14578 8625 30251 9343': ok ok
Item 4165 '4514 25064 29301 7400': ok ok
Benchmark...
real 1m11,796s
user 1m11,262s
sys 0m0,473s
EPA test...
clean
in_array_2 (pass array name, indirect reference, for-each-loop over array items):
Verification...
Item 2933 '17482 25789 27710 2096': ok ok
Item 3584 '876 14586 20885 8567': ok ok
Item 872 '176 19749 27265 18038': ok ok
Item 595 '6597 31710 13266 8813': ok ok
Item 748 '569 9200 28914 11297': ok ok
Item 3791 '26477 13218 30172 31532': ok ok
Item 2900 '3059 8457 4879 16634': ok ok
Item 676 '23511 686 589 7265': ok ok
Item 2248 '31351 7961 17946 24782': ok ok
Item 511 '8484 23162 11050 426': ok ok
Benchmark...
real 1m11,524s
user 1m11,086s
sys 0m0,437s
EPA test...
clean
in_array_3 (pass array name, nameref reference, c-style for-loop over array items by index):
Verification...
Item 1589 '747 10250 20133 29230': ok ok
Item 488 '12827 18892 31996 1977': ok ok
Item 801 '19439 25243 24485 24435': ok ok
Item 2588 '17193 18893 21610 9302': ok ok
Item 4436 '7100 655 8847 3068': ok ok
Item 2620 '19444 6457 28835 24717': ok ok
Item 4398 '4420 16336 612 4255': ok ok
Item 2430 '32397 2402 12631 29774': ok ok
Item 3419 '906 5361 32752 7698': ok ok
Item 356 '9776 16485 20838 13330': ok ok
Benchmark...
real 1m17,037s
user 1m17,019s
sys 0m0,005s
EPA test...
clean
in_array_4 (pass array items, for-each-loop over arguments):
Verification...
Item 1388 '7932 15114 4025 15625': ok ok
Item 3900 '23863 25328 5632 2752': ok ok
Item 2678 '31296 4216 17485 8874': ok ok
Item 1893 '16952 29047 29104 23384': ok ok
Item 1616 '19543 5999 4485 22929': ok ok
Item 93 '14456 2806 12829 19552': ok ok
Item 265 '30961 19733 11863 3101': ok ok
Item 4615 '10431 9566 25767 13518': ok ok
Item 576 '11726 15104 11116 74': ok ok
Item 3829 '19371 25026 6252 29478': ok ok
Benchmark...
real 1m30,912s
user 1m30,740s
sys 0m0,011s
EPA test...
clean
in_array_5 (pass array items, for-each-loop over arguments as array):
Verification...
Item 1012 '29213 31971 21483 30225': ok ok
Item 2802 '4079 5423 29240 29619': ok ok
Item 473 '6968 798 23936 6852': ok ok
Item 2183 '20734 4521 30800 2126': ok ok
Item 3059 '14952 9918 15695 19309': ok ok
Item 1424 '25784 28380 14555 21893': ok ok
Item 1087 '16345 19823 26210 20083': ok ok
Item 257 '28890 5198 7251 3866': ok ok
Item 3986 '29035 19288 12107 3857': ok ok
Item 2509 '9219 32484 12842 27472': ok ok
Benchmark...
real 1m53,485s
user 1m53,404s
sys 0m0,077s
EPA test...
clean
in_array_6 (pass array name, indirect reference + array copy, c-style for-loop over array items by index):
Verification...
Item 4691 '25498 10521 20673 14948': ok ok
Item 263 '25265 29824 3876 14088': ok ok
Item 2550 '2416 14274 12594 29740': ok ok
Item 2269 '2769 11436 3622 28273': ok ok
Item 3246 '23730 25956 3514 17626': ok ok
Item 1059 '10776 12514 27222 15640': ok ok
Item 53 '23813 13365 16022 4092': ok ok
Item 1503 '6593 23540 10256 17818': ok ok
Item 2452 '12600 27404 30960 26759': ok ok
Item 2526 '21190 32512 23651 7865': ok ok
Benchmark...
real 1m54,793s
user 1m54,326s
sys 0m0,457s
EPA test...
clean
in_array_7 (pass array items, copy array from arguments as array, for-each-loop over array items):
Verification...
Item 2212 '12127 12828 27570 7051': ok ok
Item 1393 '19552 26263 1067 23332': ok ok
Item 506 '18818 8253 14924 30710': ok ok
Item 789 '9803 1886 17584 32686': ok ok
Item 1795 '19788 27842 28044 3436': ok ok
Item 376 '4372 16953 17280 4031': ok ok
Item 4846 '19130 6261 21959 6869': ok ok
Item 2064 '2357 32221 22682 5814': ok ok
Item 4866 '10928 10632 19175 14984': ok ok
Item 1294 '8499 11885 5900 6765': ok ok
Benchmark...
real 2m35,012s
user 2m33,578s
sys 0m1,433s
EPA test...
clean
in_array_8 (pass array items, while-loop, shift over arguments):
Verification...
Item 134 '1418 24798 20169 9501': ok ok
Item 3986 '12160 12021 29794 29236': ok ok
Item 1607 '26633 14260 18227 898': ok ok
Item 2688 '18387 6285 2385 18432': ok ok
Item 603 '1421 306 6102 28735': ok ok
Item 625 '4530 19718 30900 1938': ok ok
Item 4033 '9968 24093 25080 8179': ok ok
Item 310 '6867 9884 31231 29173': ok ok
Item 661 '3794 4745 26066 22691': ok ok
Item 4129 '3039 31766 6714 4921': ok ok
Benchmark...
real 5m51,097s
user 5m50,566s
sys 0m0,495s
EPA test...
clean
in_array_9 (pre-generated array map, pass array map name, direct test without loop):
Verification...
Item 3696 '661 6048 13881 26901': ok ok
Item 815 '29729 13733 3935 20697': ok ok
Item 1076 '9220 3405 18448 7240': ok ok
Item 595 '8912 2886 13678 24066': ok ok
Item 2803 '13534 23891 5344 652': ok ok
Item 1810 '12528 32150 7050 1254': ok ok
Item 4055 '21840 7436 1350 15443': ok ok
Item 2416 '19550 28434 17110 31203': ok ok
Item 1630 '21054 2819 7527 953': ok ok
Item 1044 '30152 22211 22226 6950': ok ok
Benchmark...
real 0m0,128s
user 0m0,128s
sys 0m0,000s
EPA test...
clean
a=(b c d)
if printf '%s\0' "${a[#]}" | grep -Fqxz c
then
echo 'array “a” contains value “c”'
fi
If you prefer you can use equivalent long options:
--fixed-strings --quiet --line-regexp --null-data
Borrowing from Dennis Williamson's answer, the following solution combines arrays, shell-safe quoting, and regular expressions to avoid the need for: iterating over loops; using pipes or other sub-processes; or using non-bash utilities.
declare -a array=('hello, stack' one 'two words' words last)
printf -v array_str -- ',,%q' "${array[#]}"
if [[ "${array_str},," =~ ,,words,, ]]
then
echo 'Matches'
else
echo "Doesn't match"
fi
The above code works by using Bash regular expressions to match against a stringified version of the array contents. There are six important steps to ensure that the regular expression match can't be fooled by clever combinations of values within the array:
Construct the comparison string by using Bash's built-in printf shell-quoting, %q. Shell-quoting will ensure that special characters become "shell-safe" by being escaped with backslash \.
Choose a special character to serve as a value delimiter. The delimiter HAS to be one of the special characters that will become escaped when using %q; that's the only way to guarantee that values within the array can't be constructed in clever ways to fool the regular expression match. I choose comma , because that character is the safest when eval'd or misused in an otherwise unexpected way.
Combine all array elements into a single string, using two instances of the special character to serve as delimiter. Using comma as an example, I used ,,%q as the argument to printf. This is important because two instances of the special character can only appear next to each other when they appear as the delimiter; all other instances of the special character will be escaped.
Append two trailing instances of the delimiter to the string, to allow matches against the last element of the array. Thus, instead of comparing against ${array_str}, compare against ${array_str},,.
If the target string you're searching for is supplied by a user variable, you must escape all instances of the special character with a backslash. Otherwise, the regular expression match becomes vulnerable to being fooled by cleverly-crafted array elements.
Perform a Bash regular expression match against the string.
This is working for me:
# traditional system call return values-- used in an `if`, this will be true when returning 0. Very Odd.
contains () {
# odd syntax here for passing array parameters: http://stackoverflow.com/questions/8082947/how-to-pass-an-array-to-a-bash-function
local list=$1[#]
local elem=$2
# echo "list" ${!list}
# echo "elem" $elem
for i in "${!list}"
do
# echo "Checking to see if" "$i" "is the same as" "${elem}"
if [ "$i" == "${elem}" ] ; then
# echo "$i" "was the same as" "${elem}"
return 0
fi
done
# echo "Could not find element"
return 1
}
Example call:
arr=("abc" "xyz" "123")
if contains arr "abcx"; then
echo "Yes"
else
echo "No"
fi
The answer with most votes is very concise and clean, but it can have false positives when a space is part of one of the array elements. This can be overcome when changing IFS and using "${array[*]}" instead of "${array[#]}". The method is identical, but it looks less clean. By using "${array[*]}", we print all elements of $array, separated by the first character in IFS. So by choosing a correct IFS, you can overcome this particular issue. In this particular case, we decide to set IFS to an uncommon character $'\001' which stands for Start of Heading (SOH)
$ array=("foo bar" "baz" "qux")
$ IFS=$'\001'
$ [[ "$IFS${array[*]}$IFS" =~ "${IFS}foo${IFS}" ]] && echo yes || echo no
no
$ [[ "$IFS${array[*]}$IFS" =~ "${IFS}foo bar${IFS}" ]] && echo yes || echo no
yes
$ unset IFS
This resolves most issues false positives, but requires a good choice of IFS.
note: If IFS was set before, it is best to save it and reset it instead of using unset IFS
related:
Accessing bash command line args $# vs $*
One-line check without 'grep' and loops
if ( dlm=$'\x1F' ; IFS="$dlm" ; [[ "$dlm${array[*]}$dlm" == *"$dlm${item}$dlm"* ]] ) ; then
echo "array contains '$item'"
else
echo "array does not contain '$item'"
fi
This approach uses neither external utilities like grep nor loops.
What happens here, is:
we use a wildcard substring matcher to find our item in the array that is concatenated into a string;
we cut off possible false positives by enclosing our search item between a pair of delimiters;
we use a non-printable character as delimiter, to be on the safe side;
we achieve our delimiter being used for array concatenation too by temporary replacement of the IFS variable value;
we make this IFS value replacement temporary by evaluating our conditional expression in a sub-shell (inside a pair of parentheses)
Stop the madness! Make your solution simple, clean, and reusable.
These functions account for indexed arrays and associative arrays. They can be improved upon by upgrading the search algorithm from a linear search to a binary one (for large data sets).
##
# Determines if a value exists in an array.
###
function hasArrayValue ()
{
local -r needle="{$1:?}"
local -nr haystack="{$2:?}" # Where you pass by reference to get the entire array in one argument.
# Linear search. Upgrade to binary search for large datasets.
for value in "${haystack[#]}"; do
if [[ "$value" == "$needle" ]]; then
return 0
fi
done
return 1
}
##
# Determines if a value exists in an associative array / map.
###
function hasMapValue ()
{
local -r needle="{$1:?}"
local -nr haystack="{$2:?}"
# Linear search. Upgrade to binary search for large datasets.
for value in "${haystack[#]}"; do
if [[ $value == $needle ]]; then
return 0
fi
done
return 1
}
Yes, same logic, but in dealing with bash, if might (might) be useful to have a function with a name that lets you know what is being iterated over (or not).
given :
array=("something to search for" "a string" "test2000")
elem="a string"
then a simple check of :
if c=$'\x1E' && p="${c}${elem} ${c}" && [[ ! "${array[#]/#/${c}} ${c}" =~ $p ]]; then
echo "$elem exists in array"
fi
where
c is element separator
p is regex pattern
(The reason for assigning p separately, rather than using the expression directly inside [[ ]] is to maintain compatibility for bash 4)
Using grep and printf
Format each array member on a new line, then grep the lines.
if printf '%s\n' "${array[#]}" | grep -x -q "search string"; then echo true; else echo false; fi
example:
$ array=("word", "two words")
$ if printf '%s\n' "${array[#]}" | grep -x -q "two words"; then echo true; else echo false; fi
true
Note that this has no problems with delimeters and spaces.
A small addition to #ghostdog74's answer about using case logic to check that array contains particular value:
myarray=(one two three)
word=two
case "${myarray[#]}" in ("$word "*|*" $word "*|*" $word") echo "found" ;; esac
Or with extglob option turned on, you can do it like this:
myarray=(one two three)
word=two
shopt -s extglob
case "${myarray[#]}" in ?(*" ")"$word"?(" "*)) echo "found" ;; esac
Also we can do it with if statement:
myarray=(one two three)
word=two
if [[ $(printf "_[%s]_" "${myarray[#]}") =~ .*_\[$word\]_.* ]]; then echo "found"; fi
Combining a few of the ideas presented here you can make an elegant if statment without loops that does exact word matches.
find="myword"
array=(value1 value2 myword)
if [[ ! -z $(printf '%s\n' "${array[#]}" | grep -w $find) ]]; then
echo "Array contains myword";
fi
This will not trigger on word or val, only whole word matches. It will break if each array value contains multiple words.
I generally write these kind of utilities to operate on the name of the variable, rather than the variable value, primarily because bash can't otherwise pass variables by reference.
Here's a version that works with the name of the array:
function array_contains # array value
{
[[ -n "$1" && -n "$2" ]] || {
echo "usage: array_contains <array> <value>"
echo "Returns 0 if array contains value, 1 otherwise"
return 2
}
eval 'local values=("${'$1'[#]}")'
local element
for element in "${values[#]}"; do
[[ "$element" == "$2" ]] && return 0
done
return 1
}
With this, the question example becomes:
array_contains A "one" && echo "contains one"
etc.
Using parameter expansion:
${parameter:+word} If parameter is null or unset, nothing is
substituted, otherwise the expansion of word is substituted.
declare -A myarray
myarray[hello]="world"
for i in hello goodbye 123
do
if [ ${myarray[$i]:+_} ]
then
echo ${!myarray[$i]} ${myarray[$i]}
else
printf "there is no %s\n" $i
fi
done
The OP added the following answer themselves, with the commentary:
With help from the answers and the comments, after some testing, I came up with this:
function contains() {
local n=$#
local value=${!n}
for ((i=1;i < $#;i++)) {
if [ "${!i}" == "${value}" ]; then
echo "y"
return 0
fi
}
echo "n"
return 1
}
A=("one" "two" "three four")
if [ $(contains "${A[#]}" "one") == "y" ]; then
echo "contains one"
fi
if [ $(contains "${A[#]}" "three") == "y" ]; then
echo "contains three"
fi
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"
: NeedleInArgs "$needle" "${haystack[#]}"
: NeedleInArgs "$needle" arg1 arg2 .. argN
NeedleInArgs()
{
local a b;
printf -va '\n%q\n' "$1";
printf -vb '%q\n' "${#:2}";
case $'\n'"$b" in (*"$a"*) return 0;; esac;
return 1;
}
Use like:
NeedleInArgs "$needle" "${haystack[#]}" && echo "$needle" found || echo "$needle" not found;
For bash v3.1 and above (printf -v support)
No forks nor external programs
No loops (except internal expansions within bash)
Works for all possible values and arrays, no exceptions, nothing to worry about
Can also be used directly like in:
if NeedleInArgs "$input" value1 value2 value3 value4;
then
: input from the list;
else
: input not from list;
fi;
For bash from v2.05b to v3.0 printf lacks -v, hence this needs 2 additional forks (but no execs, as printf is a bash builtin):
NeedleInArgs()
{
case $'\n'"`printf '%q\n' "${#:2}"`" in
(*"`printf '\n%q\n' "$1"`"*) return 0;;
esac;
return 1;
}
Note that I tested the timing:
check call0: n: t4.43 u4.41 s0.00 f: t3.65 u3.64 s0.00 l: t4.91 u4.90 s0.00 N: t5.28 u5.27 s0.00 F: t2.38 u2.38 s0.00 L: t5.20 u5.20 s0.00
check call1: n: t3.41 u3.40 s0.00 f: t2.86 u2.84 s0.01 l: t3.72 u3.69 s0.02 N: t4.01 u4.00 s0.00 F: t1.15 u1.15 s0.00 L: t4.05 u4.05 s0.00
check call2: n: t3.52 u3.50 s0.01 f: t3.74 u3.73 s0.00 l: t3.82 u3.80 s0.01 N: t2.67 u2.67 s0.00 F: t2.64 u2.64 s0.00 L: t2.68 u2.68 s0.00
call0 and call1 are different variants of calls to another fast pure-bash-variant
call2 is this here.
N=notfound F=firstmatch L=lastmatch
lowercase letter is short array, uppercase is long array
As you can see, this variant here has a very stable runtime, so it does not depend that much on the match position. The runtime is dominated mostly by array length. The runtime of the searching variant is highly depending on the match position. So in edge cases this variant here can be (much) faster.
But very important, the searching variant is much mor RAM efficient, as this variant here always transforms the whole array into a big string.
So if your RAM is tight and you expect mostly early matches, then do not use this here. However if you want a predictable runtime, have long arrays to match expect late or no match at all, and also double RAM use is not much of a concern, then this here has some advantage.
Script used for timing test:
in_array()
{
local needle="$1" arrref="$2[#]" item
for item in "${!arrref}"; do
[[ "${item}" == "${needle}" ]] && return 0
done
return 1
}
NeedleInArgs()
{
local a b;
printf -va '\n%q\n' "$1";
printf -vb '%q\n' "${#:2}";
case $'\n'"$b" in (*"$a"*) return 0;; esac;
return 1;
}
loop1() { for a in {1..100000}; do "$#"; done }
loop2() { for a in {1..1000}; do "$#"; done }
run()
{
needle="$5"
arr=("${#:6}")
out="$( ( time -p "loop$2" "$3" ) 2>&1 )"
ret="$?"
got="${out}"
syst="${got##*sys }"
got="${got%"sys $syst"}"
got="${got%$'\n'}"
user="${got##*user }"
got="${got%"user $user"}"
got="${got%$'\n'}"
real="${got##*real }"
got="${got%"real $real"}"
got="${got%$'\n'}"
printf ' %s: t%q u%q s%q' "$1" "$real" "$user" "$syst"
[ -z "$rest" ] && [ "$ret" = "$4" ] && return
printf 'FAIL! expected %q got %q\n' "$4" "$ret"
printf 'call: %q\n' "$3"
printf 'out: %q\n' "$out"
printf 'rest: %q\n' "$rest"
printf 'needle: %q\n' "$5"
printf 'arr: '; printf ' %q' "${#:6}"; printf '\n'
exit 1
}
check()
{
printf 'check %q: ' "$1"
run n 1 "$1" 1 needle a b c d
run f 1 "$1" 0 needle needle a b c d
run l 1 "$1" 0 needle a b c d needle
run N 2 "$1" 1 needle "${rnd[#]}"
run F 2 "$1" 0 needle needle "${rnd[#]}"
run L 2 "$1" 0 needle "${rnd[#]}" needle
printf '\n'
}
call0() { chk=("${arr[#]}"); in_array "$needle" chk; }
call1() { in_array "$needle" arr; }
call2() { NeedleInArgs "$needle" "${arr[#]}"; }
rnd=()
for a in {1..1000}; do rnd+=("$a"); done
check call0
check call1
check call2
I see a few ways to approach the problem.
For index array using grep
grep ${value} <<< ${array[*]} && true || false
For associative array keys using grep
grep ${value} <<< "${!array[*]}" && true || false
One could use awk, but it's probably overkill.
awk --assign "v=${value}" '$v~$0 {print true}' <<<"${!array[*]}
Case statement.
case "${array[*]}" in (*${value}*) true ;; (*) false ;; esac
Bash conditional expressions in ksh88 style double square bracket:
[[ ${array[#]} =~ ${value} ]] && true || false
note: the order is important, the regex is on the right side of =~ match operator.
Bash for loop
for ((i=0;i<"${#array[*]}";i++)) ; [[ ${array[i]} = $value ]] && break 0 &> /dev/null || continue; done
Note, in this special case the truthy logic is inversed, I.E. 1=true, 0=false. That is because we use break 0 to force break builtin to have an exit code besides true, which is always the case unless the break n parameter is less than 1. We imperatively want to break the loop, and we want a boolean exit code besides default 'true', so in this case we flip the logic. For that reason it would probably make more sense to use a function with return true semantics.
After having answered, I read another answer that I particularly liked, but it was flawed and downvoted. I got inspired and here are two new approaches I see viable.
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>

Resources