Есть ли в bash / bourne оператор in?

17

Я ищу оператор «in», который работает примерно так:

if [ "$1" in ("cat","dog","mouse") ]; then
    echo "dollar 1 is either a cat or a dog or a mouse"
fi

Это, очевидно, гораздо более короткое утверждение по сравнению, скажем, с использованием нескольких «или» тестов.

mrjayviper
источник

Ответы:

31

Вы можете использовать case...esac

$ cat in.sh 
#!/bin/bash

case "$1" in 
  "cat"|"dog"|"mouse")
    echo "dollar 1 is either a cat or a dog or a mouse"
  ;;
  *)
    echo "none of the above"
  ;;
esac

Ex.

$ ./in.sh dog
dollar 1 is either a cat or a dog or a mouse
$ ./in.sh hamster
none of the above

С помощью ksh, bash -O extglobили zsh -o kshglob, вы также можете использовать расширенный шаблон glob:

if [[ "$1" = @(cat|dog|mouse) ]]; then
  echo "dollar 1 is either a cat or a dog or a mouse"
else
  echo "none of the above"
fi

С bash, ksh93или zsh, вы также можете использовать сравнение регулярных выражений:

if [[ "$1" =~ ^(cat|dog|mouse)$ ]]; then
  echo "dollar 1 is either a cat or a dog or a mouse"
else
  echo "none of the above"
fi
steeldriver
источник
есть ли причина для двойных скобок? Еще раз спасибо!
mrjayviper
1
@mrjayviper двойные скобки - это расширенная тестовая конструкция - AFAIK оператор regex =~недопустим в тесте с одиночными скобками POSIX
steeldriver
1
@steeldriver Only [- это POSIX, но [[это расширенная функция bash, ksh (по-видимому, оттуда и zsh. caseexample является наиболее POSIX из всех, хотя
Сергей Колодяжный,
2
@ StéphaneChazelas Поскольку, по крайней мере, в bash 4.1-alpha нет необходимости явно устанавливать extglog. Из изменений bash : s. Временное принудительное включение extglob при разборе аргумента шаблона для операторов == и! = Для команды [[, для совместимости.
Исаак
@steeldriver $ 1 в case "$1" inне нужно заключать в кавычки, ни разделение слов, ни расширение пути не выполняются в этом токене.
Исаак
9

В bash нет теста "in", но есть тест на регулярные выражения (не в bourne):

if [[ $1 =~ ^(cat|dog|mouse)$ ]]; then
    echo "dollar 1 is either a cat or a dog or a mouse"
fi

И обычно пишется с использованием переменной (меньше проблем с цитированием):

regex='^(cat|dog|mouse)$'

if [[ $1 =~ $regex ]]; then
    echo "dollar 1 is either a cat or a dog or a mouse"
fi

Для более старой оболочки Bourne вам нужно использовать совпадение регистра:

case $1 in
    cat|dog|mouse)   echo "dollar 1 is either a cat or a dog or a mouse";;
esac
Исаак
источник
Это принятый ответ для меня.
Nam G VU
6

Использование caseхорошо и хорошо, если у вас есть фиксированный набор домашних животных, с которыми вы хотите сравниться. Но это не сработает, если вам нужно построить шаблон во время выполнения, так как caseне интерпретирует изменение внутри расширенных параметров.

Это будет соответствовать только литеральной строке cat|dog|mouse:

patt='cat|dog|mouse'
case $1 in 
        $patt) echo "$1 matches the case" ;; 
esac

Однако вы можете использовать переменную с совпадением регулярного выражения. Пока переменная не заключена в кавычки, любые операторы регулярных выражений в ней имеют свои особые значения.

patt='cat|dog|mouse'
if [[ "$1" =~ ^($patt)$ ]]; then
        echo "$1 matches the pattern"
fi

Вы также можете использовать ассоциативные массивы. Проверка, существует ли ключ в одном из них, является самой близкой к inоператору, который дает Bash. Хотя синтаксис немного уродлив:

declare -A arr
arr[cat]=1
arr[dog]=1
arr[mouse]=1

if [ "${arr[$1]+x}" ]; then
        echo "$1 is in the array"
fi

( ${arr[$1]+x}расширяется, xесли arr[$1]установлено, в противном случае пусто. )

ilkkachu
источник
5

Вы можете использовать caseоператор в ifтесте, но код будет выглядеть немного странно:

if case "$1" in (cat|dog|mouse) true ;; (*) false; esac; then
    printf '"%s" is one of cat, dog or mouse\n' "$1"
else
    printf '"%s" is unknown\n' "$1"
fi

или немного короче,

if ! case "$1" in (cat|dog|mouse) false; esac; then
    printf '"%s" is one of cat, dog or mouse\n' "$1"
else
    printf '"%s" is unknown\n' "$1"
fi

Это использует caseпредложение только для сопоставления с шаблоном для ifпредложения. Это вводит ненужный истинный / ложный тест.

Лучше просто использовать case:

case "$1" in
    cat|dog|mouse)
        printf '"%s" is one of cat, dog or mouse\n' "$1"
        ;;
    *)
        printf '"%s" is unknown\n' "$1"
esac

Не делай этого:

is_one_of () {
    eval "case $1 in ($2) return 0; esac"
    return 1
}

if is_one_of "$1" 'cat|dog|mouse'; then
    printf '"%s" is one of cat, dog or mouse\n' "$1"
else
    printf '"%s" is unknown\n' "$1"
fi

или это:

is_one_of () (
    word=$1
    shift
    IFS='|'
    eval "case $word in ($*) return 0; esac"
    return 1
)

if is_one_of "$1" cat dog mouse; then
    printf '"%s" is one of cat, dog or mouse\n' "$1"
else
    printf '"%s" is unknown\n' "$1"
fi

... потому что вы просто добавляете более опасные слова, просто чтобы иметь возможность использовать ifоператор в своем коде вместо совершенно разумного caseутверждения.

Кусалананда
источник
Не лучше ли разделить регистр на вызов функции и оценить состояние выхода внутри оператора if?
Сергей Колодяжный
@SergiyKolodyazhnyy А пусть шаблон будет аргументом функции? Было бы должно делать evalиз caseзаявления в этом случае, и это было бы еще более склонны к ошибкам.
Кусалананда
В этом случае есть простой шаблон, каждый из которых может быть передан как позиционный параметр для работы и выполнения "$1"|"$2"|"$3". Также unix.stackexchange.com/a/234415/85039 Но да, это немного волосато.
Сергей Колодяжный
4

grep подходить.

if echo $1 | grep -qE "^(cat|dog|mouse)$"; then 
    echo "dollar 1 is either a cat or a dog or a mouse"
fi
  • -qчтобы избежать вывода на экран (быстрее, чем печатать >/dev/null).
  • -Eдля расширенных (cat|dog|mouse)аспектов регулярных выражений это необходимо.
  • ^(cat|dog|mouse)$соответствует любым строкам, начинающимся с ( ^), с кошкой, собакой или мышью ( (cat|dog|mouse)), за которыми следует конец строки ( $)
Стив
источник