Using case and arrays together in bash

10,737

Solution 1

With ksh93, thanks to this bug, you can do:

IFS='|'
ARR=( opt1 opt2 opt3 )

IFS='|'
case $1 in
  (@("${ARR[*]}"))
    echo "Option is contained in the array";;
  (*)
    echo "Option is not contained in the array";;
esac

(I wouldn't rely on it as the bug might get fixed in the future).

With zsh, you could do:

case ${ARR[(Ie)$1]}
  (0)
    echo "Option is not contained in the array";;
  (*)
    echo "Option is contained in the array";;
esac

(though, you'd probably rather want to use if ((ARR[(Ie)$1])); then echo is present... here rather than a case construct).

${array[(I)pattern]} returns the index of the last element that matches the pattern in the array, or 0 otherwise. The e flag is for exact match (as opposed to pattern match).

With bash, ksh, yash, zsh, if you're ready to assume that $ARR and $1 don't contain a certain character like @, and that $ARR won't be empty, you can do:

IFS=@
case "@${ARR[*]}@" in
  (*"@$1@"*)
    echo "Option is contained in the array";;
  (*)
    echo "Option is not contained in the array";;
esac

With bash -O extglob, zsh -o kshglob -o globsubst, you could define a helper that builds a pattern based on the elements of the array:

arraypat() {
  awk '
    BEGIN{
      if (ARGC <= 1) print "!(*)"
      else {
        for (i = 1; i < ARGC; i++) {
          gsub(/[][|<>\\?*()]/, "[&]", ARGV[i])
          s = s sep ARGV[i]
          sep = "|"
        }
        print "@(" s ")"
      }
    }' "$@"
}

case $1 in
  ($(arraypat "${ARR[@]}"))
    echo "Option is contained in the array";;
  (*)
    echo "Option is not contained in the array";;
esac

Solution 2

Not really in a compact and easy to use way. Remember that $ARR will expand to only the first element of the array, opt1 in your example.

You could use "${ARR[@]}", but using your data this would give a false positive for the string 1 opt.

With more recent versions of bash, you could consider using an associative array:

declare -A arr
arr=( [opt1]=1 [opt2]=1 [opt3]=1 )

if [[ "${arr[$1]}" -eq 1 ]]; then
   # $1 is a key in arr
else
   # is not
fi

Solution 3

Why would you want to do it with case? It's meant for string pattern matching, not per-element matching.

Frankly, if you need the "contains" test often and want to make it short because of that, just put the hard part in a function instead of using ugly workarounds:

#!/bin/bash
ARR=( foo bar doo );

contains() {
        typeset _x;
        typeset -n _A="$1"
        for _x in "${_A[@]}" ; do
                [ "$_x" = "$2" ] && return 0
        done
        return 1
}

if contains ARR "$1" ; then
        echo "\"$1\" is contained in ARR"
else
        echo "\"$1\" not contained in ARR"
fi

(That should also work in ksh)

Share:
10,737

Related videos on Youtube

red_trumpet
Author by

red_trumpet

I there, I'm studying mathematics in germany.

Updated on September 18, 2022

Comments

  • red_trumpet
    red_trumpet almost 2 years

    Is it possible to check if a variable is contained inside an array using case? I would like to do something like

    ARR=( opt1 opt2 opt3 );
    
    case $1 in
        $ARR)
            echo "Option is contained in the array";
        *)
            echo "Option is not contained in the array";
    esac
    
    • Inian
      Inian over 6 years
      Are you mandated to use case?
    • muru
      muru over 6 years
    • Stéphane Chazelas
      Stéphane Chazelas over 6 years
      @muru, I'd say it's different here because of the case requirement. Presumably in the end the OP wants to add more cases as in case $1 in $ARR1)... $ARR2)... foo) bar)...; esac
  • ilkkachu
    ilkkachu over 6 years
    Or to get full use of the associative array, i.e. use arbitrary values, not just 1, test with something like [[ "${arr[$1]+x}" ]] (it's shorter, too!)
  • Kusalananda
    Kusalananda over 6 years
    @ilkkachu Yes, or [[ -n "${arr[$1]}" ]].
  • RonJohn
    RonJohn over 6 years
    OP probably wants a builtin for the presumed speed and efficiency.
  • ilkkachu
    ilkkachu over 6 years
    @RonJohn, everything that function does, is built in to Bash. As for speed, it's hard to say, we'd need to test, but for huge data sets, associative arrays would probably be faster (than either the loop, or a case), since key lookups are presumably O(1). But for huge data, you shouldn't use the shell anyway, and for small data, minor performance differences don't matter.