99

I am looking for a construct in bash, to decide if a variable $WORD is one of defined words. I need something like this:

if "$WORD" in dog cat horse ; then 
    echo yes
else
    echo no
fi

does bash have such construct? If not, what would be the closest?

13 Answers 13

121

This is a Bash-only (>= version 3) solution that uses regular expressions:

if [[ "$WORD" =~ ^(cat|dog|horse)$ ]]; then
    echo "$WORD is in the list"
else
    echo "$WORD is not in the list"
fi

If your word list is long, you can store it in a file (one word per line) and do this:

if [[ "$WORD" =~ $(echo ^\($(paste -sd'|' /your/file)\)$) ]]; then
    echo "$WORD is in the list"
else
    echo "$WORD is not in the list"
fi

One caveat with the file approach:

  • It will break if the file has whitespace. This can be remedied by something like:

    sed 's/[[:blank:]]//g' /your/file | paste -sd '|' /dev/stdin
    

Thanks to @terdon for reminding me to properly anchor the pattern with ^ and $.

2
  • 3
    And shopt -s nocasematch might help if you want the search to be case insensitive. Commented Feb 13, 2014 at 16:27
  • If you're going to put your list of matches in a file, you might as well use grep: egrep -q "^$WORD$" /tmp/words; [[ $? != 0 ]] && echo "Not found" || echo "OK".
    – GLRoman
    Commented Aug 21, 2020 at 21:57
110
case $word in
    dog|cat|horse) echo yes;;
    *)             echo no;;
esac
0
13

How about:

#!/usr/bin/env bash

WORD="$1"
for w in dog cat horse
do
  if [ "$w" == "$WORD" ] 
  then
      yes=1
      break
  fi
done;
[ "$yes" == "1" ] && echo "$WORD is in the list" || 
                     echo "$WORD is not in the list"

Then:

$ a.sh cat
cat is in the list
$ a.sh bob
bob is not in the list
6

I just had the same problem and found a surprisingly understandable solution which does not require any regex knowledge.

This works in Bash and ksh, and in zsh with setopt kshglob:

WORD=abc
[[ "$WORD" == @(def|abc|ghi|foo|barbaz) ]] && echo Word is in set.

(Native zsh version would be the same without the @ sign).

How does this work? I don't want to repeat large portions of the bash manual here, but in my own words:

If the binary operator == is used with the compound command [[, the word to the right side of the operator undergoes pattern matching as explained in the section "Pattern Matching" in the bash manual. Furthermore, under these conditions, while matching, the shell option extglob is assumed to be set (regardless of the actual setting); this is the key point in understanding the above.

But what if the words in your set already contain characters which have a special meaning in pattern matching, command substitution and other expansions? The remedy is easy: Just use something like

[[ "$WORD" == @('a*'|abc|'?ghi'|'$DontExpandMe'|barbaz) ]] && echo Word is in set.

I found this to be a very easy, understandable and efficient (as long as $WORD is short) solution which does not have side effects or is dangerous like the set-based methods.

4
if (echo "$set"  | fgrep -q "$WORD")
3
  • 1
    Careful though, this will return true if $WORD is empty, it will match if WORD=ca or WORD=og or similar and I assume you meant echo ${set[@]}.
    – terdon
    Commented Jan 29, 2014 at 18:25
  • 2
    just add -w to grep to avoid partial words
    – BrenoZan
    Commented Jan 29, 2014 at 18:34
  • 1
    Maybe you should edit your answer to add the -w there because it's likely that the OP wanted exact words, not partial. Commented Apr 14, 2021 at 4:37
2

You could define a bash function for this:

function word_valid() 
{ 
  if [ "$1" != "cat" -a "$1" != "dog" -a "$1" != "horse" ];then
    echo no
  else
    echo yes
  fi
}

Then use simply like this:

word_valid cat
1

i was searching for a 'one line' solution to validate my script argument, and used Joseph R. answer above to come up with:

[[ "$ARG" =~ ^(true|false)$ ]] || { echo "Argument received invalid value" ; exit 1 ; }

1

You could use fgrep to specify all allowed words:

if echo "$WORD" | grep -F -wq -e dog -e cat -e horse; then 
    echo yes
else
    echo no
fi

The -w flag matches only full words, the -q flag makes it operate silently (because all we need is the return value for the if statement to use), and each -e pattern specifies a pattern to allow.

fgrep is the version of grep that does normal string matching instead of regex matching. If you have grep, you should have fgrep, but if not, it's identical to using grep with the -F flag (so you would just replace fgrep -wq above with grep -Fwq).

1

I know this is old, but a trick I use is to see if the list is equal to the query plus padding on both sides.

list=" cat dog horse "
query="cat"
if [[ "$list" == *" $query "* ]]; then echo true; else echo false; fi

I added spaces in my list, and also around my query, to make sure I don't accidentally catch "scat" or some partial match.

Note the asterisks must be outside the quotes for bash to expand it correctly.

1
  • This would return the string true if query was e.g. dog horse.
    – Kusalananda
    Commented Jun 15, 2022 at 5:54
0

You may want to put the list of words into a file, in case you change the list often, or you want it to be shared by multiple scripts.  And you may need to put the words into a file if the list gets too long to manage in a script.  Then you can say

if fgrep –qx "$WORD" word_list
1
  • 1
    no, I don't want to have my list in a file Commented Jan 30, 2014 at 9:30
0

For this purpose associate arrays together with test -v (checks if variable exists) are a good solution when you need an exact match:

#!/bin/bash
# declare associative array with all animals and no value
declare -A ANIMALS=(["dog"]= ["cat"]= ["horse"]=)

test_animal() {
  # check if parameter is found within the associative array
  if [[ -v ANIMALS[$1] ]] ; then
    echo "$1 is an animal"
  else
    echo "$1 is NOT an animal"
  fi
}

test_animal "sun"
test_animal "tree"
test_animal "dog"
test_animal "cat"
test_animal "horse"

Output:

sun is NOT an animal
tree is NOT an animal
dog is an animal
cat is an animal
horse is an animal
-1

This worked for me:

#!/bin/bash
phrase=(cat dog parrot cow horse)
findthis=parrot

for item in ${phrase[*]}
do
    test "$item" == "$findthis" && { echo "$findthis found!"; break; }
done
-1

If words are a list where values are separated by a newline, you can do:

WORDS="$(ls -1)"
if echo "${WORDS}" | grep --quiet --line-regexp --fixed-strings "${WORD}"; then
    echo yes
else
    echo no
fi

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .