Existe um operador "in" em bash / bourne?

17

Estou procurando um operador "in" que funcione dessa maneira:

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

É obviamente uma afirmação muito mais curta em comparação com, digamos, o uso de vários testes "ou".

mrjayviper
fonte

Respostas:

31

Você pode usar 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

Com ksh, bash -O extglobou zsh -o kshglob, você também pode usar um padrão glob extendido:

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

Com bash, ksh93ou zsh, você também pode usar uma comparação de expressão regular:

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
chave de aço
fonte
alguma razão para os colchetes duplos? Obrigado novamente!
Mrjayviper
1
@mrjayviper os suportes duplos são uma construo de teste estendido - AFAIK o operador expressão regular =~não é válida no interior do POSIX suporte único teste
steeldriver
1
@steeldriver Só [é POSIX, mas [[é característica prolongado de bash, ksh (aparentemente é originado a partir de lá, e zsh. caseexemplo é mais POSIX de tudo, porém
Sergiy Kolodyazhnyy
2
@ StéphaneChazelas Desde pelo menos bash 4.1-alpha, não há necessidade de definir extglog explicitamente. Das alterações do bash : s. Force o extglob temporariamente ao analisar o argumento padrão nos operadores == e! = No comando [[, para compatibilidade.
Isaac
@steeldriver O $ 1 in case "$1" innão precisa ser citado, nenhuma divisão de palavras nem expansão de nome de caminho são executadas nesse token.
Isaac
9

Não existe um teste "in" no bash, mas existe um teste regex (não no bourne):

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

E geralmente escrito usando uma variável (menos problemas com citações):

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

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

Para um shell Bourne mais antigo, você precisa usar uma correspondência de caso:

case $1 in
    cat|dog|mouse)   echo "dollar 1 is either a cat or a dog or a mouse";;
esac
Isaac
fonte
Esta é a resposta aceita para mim.
Nam G VU
6

O uso de um caseé bom e bem, se você tiver um conjunto fixo de animais de estimação com quem deseja comparar. Mas não funcionará se você precisar criar o padrão em tempo de execução, pois casenão interpreta a alternância de dentro de parâmetros expandidos.

Isso corresponderá apenas à cadeia literal cat|dog|mouse:

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

No entanto, você pode usar uma variável com a correspondência de expressão regular. Contanto que a variável não seja citada, quaisquer operadores de expressões regulares têm seus significados especiais.

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

Você também pode usar matrizes associativas. Verificar se existe uma chave em uma é a coisa mais próxima de um inoperador que Bash fornece. Embora a sintaxe seja um pouco feia:

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}expande para xse arr[$1]estiver definido, vazio caso contrário. )

ilkkachu
fonte
5

Você poderia usar uma casedeclaração em um ifteste, mas o código ficaria um pouco cabeludo:

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

ou um pouco mais curto,

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

Isso está usando uma casecláusula apenas para fazer a correspondência de padrões para a ifcláusula. Introduz um teste verdadeiro / falso desnecessário.

É melhor usar apenas 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

Não faça isso:

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

ou isto:

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

... porque você está apenas adicionando itens mais perigosos, apenas para poder usar uma ifinstrução em seu código no lugar de uma caseinstrução perfeitamente razoável .

Kusalananda
fonte
Não seria melhor dividir caso em chamada de função e avaliar o status de saída dentro da instrução if?
Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy E deixe o padrão ser um argumento para a função? Ele teria que fazer um evalda casedeclaração nesse caso, e seria ainda mais propenso a erros.
Kusalananda
Nesse caso, existe um padrão simples, cada um pode ser passado como parâmetro posicional para funcionar e executar "$1"|"$2"|"$3". Também unix.stackexchange.com/a/234415/85039 Mas sim, é um pouco cabeludo.
Sergiy Kolodyazhnyy
4

grep abordagem.

if echo $1 | grep -qE "^(cat|dog|mouse)$"; then 
    echo "dollar 1 is either a cat or a dog or a mouse"
fi
  • -qpara evitar qualquer saída na tela (digitar mais rápido que >/dev/null).
  • -Epara expressões regulares estendidas, os (cat|dog|mouse)aspectos precisam disso.
  • ^(cat|dog|mouse)$corresponde a qualquer linha que comece ( ^) com gato, cachorro ou mouse ( (cat|dog|mouse)) seguida do final da linha ( $)
Steve
fonte