38

Suppose I have a non-associative array that has been defined like

my_array=(foo bar baz)

How can I check whether the array contains a given string? I’d prefer a solution that can be used within the conditional of an if block (e.g. if contains $my_array "something"; then ...).

bdesham
  • 1,307
  • 2
  • 13
  • 23

2 Answers2

47
array=(foo bar baz foo)
pattern=f*
value=foo

if (($array[(I)$pattern])); then
  echo array contains at least one value that matches the pattern
fi

if (($array[(Ie)$value])); then
  echo value is amongst the values of the array
fi

$array[(I)foo] returns the index of the last occurrence of foo in $array and 0 if not found. The e flag is for it to be an exact match instead of a pattern match.

To check the $value is among a literal list of values, you could pass that list of values to an anonymous function and look for the $value in $@ in the body of the function:

if ()(( $@[(Ie)$value] )) foo bar baz and some more; then
  echo "It's one of those"
fi

To know how many times the value is found in the array, you could use the ${A:*B} operator (elements of array A that are also in array B):

array=(foo bar baz foo)
value=foo
search=("$value")
(){print -r $# occurrence${2+s} of $value in array} "${(@)array:*search}"

Or using pattern matching on the array elements:

(){print -r $# occurrence${2+s} of $value in array} "${(M@)array:#$value}"
  • there is a closing brace missing in the first if (edit remains below the 6 character minimum …): if ((${array[(I)$pattern]})); then – pseyfert May 18 '19 at 10:03
  • @pseyfert, thanks. Fixed now. I opted for removing the {...} which makes it more legible. They are not necessary in zsh like in csh/rc/tcl/perl but unlike in ksh/bash/yash. – Stéphane Chazelas May 18 '19 at 11:19
  • how to do it with an array literal? Trying if (((a b c)[(I)$(./command | head -1)])) leads to 'bad math expression' error. – minseong Oct 02 '20 at 00:46
  • 1
    @theonlygusti, see edit. Note that there's no such thing as an array literal in zsh (or any shell for that matters), only an array declaration syntax (var=(some elements) or set -A var some elements or set var some elements depending on the shell), and list of arguments passed to commands or to for var in... – Stéphane Chazelas Jan 04 '21 at 18:27
  • The anonymous function is defined ()((body))? – Timo Sep 27 '21 at 18:09
  • 1
    @Timo, more like () body where the body here is an arithmetic expression (((...))). It's similar to a normal function but with the function name omitted and can take arguments (not when the body is a simple command though), so () { echo "$1"; } foo or () (echo "$1") foo just like you'd do f() (echo "$1"); f foo in any POSIX shell, or f() (($1 == $2)); f 1+1 2 in Korn-like shells. – Stéphane Chazelas Sep 27 '21 at 18:15
  • ok, besides being anonymous it is called and defined at the same time. Seems like you can omit the curly braces to surround the body. – Timo Sep 27 '21 at 18:18
  • 1
    @Timo, it's similar to the lambdas of the es shell (as in @ { echo $1 } foo there) – Stéphane Chazelas Sep 27 '21 at 18:20
30

If you have an array $my_array and you want to know whether it contains the string foo, one possible test is

[[ ${my_array[(ie)foo]} -le ${#my_array} ]]

The full, exact value of the array element must be foo; it’s not a substring check or anything like that.

If you want to see whether the value of the variable $my_string is in the array, use

[[ ${my_array[(ie)$my_string]} -le ${#my_array} ]]

This (ie) syntax isn’t very obvious. It’s explained in the Subscript Flags section of the ZSH manual. The i part means that we are using “reverse subscripting”: instead of passing in a subscript and obtaining a value, like we do with the usual ${my_array[1]}, we’re passing a value and asking for the first subscript that would give this value. This subscript is numerical and 1-based (the first element of the array is at index 1), which is different from the convention used by most programming languages. The e in (ie) means that we want an exact match, without expanding pattern-matching characters like *.

If the value is not found in the array, ${my_array[(ie)foo] will evaluate to the first index past the end of the array, so for a 3-element array it would return 4. ${#my_array} gives the index of the last element of the array, so if the former is less than or equal to the latter then the given value is present in the array somewhere. If you want to check whether a given value is not in the array then change the “less than or equal to” to a “greater than”:

[[ ${my_array[(ie)foo]} -gt ${#my_array} ]]
bdesham
  • 1,307
  • 2
  • 13
  • 23