Como verifico se existe uma variável em uma lista no BASH

137

Estou tentando escrever um script no bash que verifique a validade de uma entrada do usuário.
Quero corresponder a entrada (digamos variável x) a uma lista de valores válidos.

o que eu inventei no momento é:

for item in $list
do
    if [ "$x" == "$item" ]; then
        echo "In the list"
        exit
    fi
done

Minha pergunta é se existe uma maneira mais simples de fazer isso,
algo como um list.contains(x)para a maioria das linguagens de programação.

Adição: a
lista de palavras é:

list="11 22 33"

meu código ecoará a mensagem apenas para esses valores, uma vez que listé tratada como uma matriz e não como uma string, todas as manipulações de string serão validadas 1enquanto eu gostaria que ela falhasse.

Ofir Farchy
fonte

Respostas:

142
[[ $list =~ (^|[[:space:]])$x($|[[:space:]]) ]] && echo 'yes' || echo 'no'

ou crie uma função:

contains() {
    [[ $1 =~ (^|[[:space:]])$2($|[[:space:]]) ]] && exit(0) || exit(1)
}

para usá-lo:

contains aList anItem
echo $? # 0: match, 1: failed
chemila
fonte
37
deve ser[[ $list =~ (^| )$x($| ) ]] && echo 'yes' || echo 'no'
Matvey Aksenov
12
Pode dar falso positivo se a entrada do usuário contém regulares personagens expressão especiais, por exemplox=.
Glenn Jackman
4
Isto lhe dará nenhum falso-positivos / negativos: contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; }.
Skokin #
6
uma solução mais sucinta: [[ " $list " =~ " $x " ]] && echo 'yes' || echo 'no'. -lo do espaço assumindo correto é o separador e $xnão contém espaço
Tianren Liu
2
Eu acho que é melhor usar uma função "está em" isIn()para que você possa escrever o item primeiro nos parâmetros. Além disso, você pode echousar algo assim, em vez de usar o exitseguinte: [[ $2 =~ (^|[[:space:]])$1($|[[:space:]]) ]] && echo 1 || echo 0Para que você possa usar a função desta maneira:result=$(isIn "-t" "-o -t 45") && echo $result
hayj
34

Matvey está certo, mas você deve citar $ x e considerar qualquer tipo de "espaço" (por exemplo, nova linha) com

[[ $list =~ (^|[[:space:]])"$x"($|[[:space:]]) ]] && echo 'yes' || echo 'no' 

então, ie

# list_include_item "10 11 12" "2"
function list_include_item {
  local list="$1"
  local item="$2"
  if [[ $list =~ (^|[[:space:]])"$item"($|[[:space:]]) ]] ; then
    # yes, list include item
    result=0
  else
    result=1
  fi
  return $result
}

fim então

`list_include_item "10 11 12" "12"`  && echo "yes" || echo "no"

ou

if `list_include_item "10 11 12" "1"` ; then
  echo "yes"
else 
  echo "no"
fi

Observe que você deve usar ""no caso de variáveis:

`list_include_item "$my_list" "$my_item"`  && echo "yes" || echo "no"
Oriettaxx
fonte
3
Essa solução funciona mesmo que $itemcontenha caracteres especiais, como .(mas a $listvariável provavelmente precisa ser citada dentro do teste). E a função pode ser definida ainda mais simples:contains () { [[ "$1" =~ (^|[[:space:]])"$2"($|[[:space:]]) ]]; } .
Skokin #
não funciona em casos como: list="aa bb xx cc ff" ex="aa bb"
creativeChips
Olá. Estou tentando aprender os fundamentos do bashscript e queria saber como você pode analisar a seqüência de lista e a lista, e você faz a verificação existente nessa lista. Entendo que você se dividiu usando o espaço, mas não consegui entender completamente a expressão. Você pode me fornecer as palavras-chave no Google os fundamentos desta expressão: $list =~ (^|[[:space:]])"$item"($|[[:space:]]). Ou, se você tiver tempo, ficaria feliz em ouvir sua explicação para essa expressão. Nota: Eu acho que é uma expressão de expressão regular (começa com ^ etc), mas não sei o que = ~ significa. Então, eu adoraria uma explicação geral: P
Ali Yılmaz
28

A solução mais fácil do IMHO é anexar e anexar a sequência original com um espaço e comparar com uma regex com [[ ]]

haystack='foo bar'
needle='bar'

if [[ " $haystack " =~ .*\ $needle\ .* ]]; then
    ...
fi

isso não será falso positivo em valores com valores contendo a agulha como substring, por exemplo, com um palheiro foo barbaz.

(O conceito é descaradamente roubado do JQuery's hasClass()-Method)

blaimi
fonte
4
se o separador não é espaço, a solução é mais óbvia. isso também pode ser feito sem regex: haystack="foo:bar"e[[ ":$haystack:" = *:$needle:* ]]
phiphi
25

e quanto a

echo $list | grep -w $x

você pode verificar a saída ou a $?linha acima para tomar a decisão.

grep -w verifica padrões de palavras inteiras.

Kent
fonte
1
não seria esta validar "val" se "valor" é válido (ou seja, na lista), uma vez que é a sua substring
Ofir Farchy
Sim, assim como a resposta aceita. @glennjackman fornece uma solução funcional.
f.ardelian
3
você pode usar grep -q para fazer grep para ficar quieto
Amir
isso também aceitar resultados parciais, o que pode não ser o que o usuário quer
Carnicer
3
@carnicer então é só usar grep -w $ x para ter uma correspondência exata
user1297406
18

Você também pode usar (* curingas) fora de uma declaração de caso, se usar colchetes duplos:

string='My string';

if [[ $string == *My* ]]
then
echo "It's there!";
fi
Mithun Sasidharan
fonte
3
Simples e elegante, +1
João Pereira
14
Isso dará um falso positivo se "My" for uma substring de outra string, por exemplo, string = 'MyCommand string'.
Luke Lee
Vale a pena notar que os curingas ( *My*) devem estar no lado direito do teste.
Jacob Vanus
8

Se sua lista de valores deve ser codificada no script, é bastante simples testar usando case. Aqui está um pequeno exemplo, que você pode adaptar aos seus requisitos:

for item in $list
do
    case "$x" in
      item1|item2)
        echo "In the list"
        ;;
      not_an_item)
        echo "Error" >&2
        exit 1
        ;;
    esac
done

Se a lista for uma variável de matriz em tempo de execução, uma das outras respostas provavelmente será um ajuste melhor.

Toby Speight
fonte
7

Se a lista estiver corrigida no script, eu gosto do seguinte:

validate() {
    grep -F -q -x "$1" <<EOF
item 1
item 2
item 3
EOF
}

Em seguida, use validate "$x"para testar se $xé permitido.

Se você deseja uma linha única e não se importa com espaços em branco nos nomes dos itens, pode usar isso (observe em -wvez de -x):

validate() { echo "11 22 33" | grep -F -q -w "$1"; }

Notas:

  • Isso é compatível shcom POSIX .
  • validatese não aceitar substrings (remover a -xopção de grep se você quer isso).
  • validateinterpreta seu argumento como uma sequência fixa, não como uma expressão regular (remova a -Fopção grep, se desejar).

Código de exemplo para exercer a função:

for x in "item 1" "item2" "item 3" "3" "*"; do
    echo -n "'$x' is "
    validate "$x" && echo "valid" || echo "invalid"
done
Søren Løvborg
fonte
6

Considere explorar as chaves de matrizes associativas . Eu presumo que isso supera a correspondência de regex / padrão e loop, embora eu não o tenha perfilado.

declare -A list=( [one]=1 [two]=two [three]='any non-empty value' )
for value in one two three four
do
    echo -n "$value is "
    # a missing key expands to the null string, 
    # and we've set each interesting key to a non-empty value
    [[ -z "${list[$value]}" ]] && echo -n '*not* '
    echo "a member of ( ${!list[*]} )"
done

Resultado:

one is a member of ( one two three )
two is a member of ( one two three )
three is a member of ( one two three )
four is *not* a member of ( one two three )
RubyTuesdayDONO
fonte
2
... e você pode simplificar que a substituição (e não contar com echo -n) com uso criativo de parâmetros: do is="${list[$value]+is }"; echo "$value ${is:-is *not* }a member of ( ${!list[*]} )"; done.
Toby Speight
Existe uma maneira fácil de criar essa matriz se eu tiver apenas uma lista (longa) como `list =" one two three xyz ... "?
Ott Toomet
5

Acho mais fácil usar o formulário echo $LIST | xargs -n1 echo | grep $VALUEconforme ilustrado abaixo:

LIST="ITEM1 ITEM2"
VALUE="ITEM1"
if [ -n "`echo $LIST | xargs -n1 echo | grep -e \"^$VALUE`$\" ]; then
    ...
fi

Isso funciona para uma lista separada por espaço, mas você pode adaptá-la a qualquer outro delimitador (como :) fazendo o seguinte:

LIST="ITEM1:ITEM2"
VALUE="ITEM1"
if [ -n "`echo $LIST | sed 's|:|\\n|g' | grep -e \"^$VALUE`$\"`" ]; then
   ...
fi

Observe que "são necessários para o teste funcionar.

Sébastien Pierre
fonte
Isso fará com que LIST="SOMEITEM1 ITEM2"retornar true mesmo que ITEM1não está nele
Ofir Farchy
Boa captura, atualizei o exemplo com grep -e para excluir correspondência parcial.
Sébastien Pierre
Eu acredito que há um `extra no final da declaração" se ". O formato correto é: [-n "` echo $ LIST | xargs -n1 echo | grep -e \ "^ $ VALUE $ \"]
Elad Tabak
3

Pensei em adicionar minha solução à lista.

# Checks if element "$1" is in array "$2"
# @NOTE:
#   Be sure that array is passed in the form:
#       "${ARR[@]}"
elementIn () {
    # shopt -s nocasematch # Can be useful to disable case-matching
    local e
    for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
    return 1
}

# Usage:
list=(11 22 33)
item=22

if elementIn "$item" "${list[@]}"; then
    echo TRUE;
else
    echo FALSE
fi
# TRUE

item=44
elementIn $item "${list[@]}" && echo TRUE || echo FALSE
# FALSE
SamWN
fonte
1

Exemplos

$ in_list super test me out
NO

$ in_list "super dude" test me out
NO

$ in_list "super dude" test me "super dude"
YES

# How to use in another script
if [ $(in_list $1 OPTION1 OPTION2) == "NO" ]
then
  echo "UNKNOWN type for param 1: Should be OPTION1 or OPTION2"
  exit;
fi

na lista

function show_help()
{
  IT=$(CAT <<EOF

  usage: SEARCH_FOR {ITEM1} {ITEM2} {ITEM3} ...

  e.g. 

  a b c d                    -> NO
  a b a d                    -> YES
  "test me" how "test me"    -> YES

  )
  echo "$IT"
  exit
}

if [ "$1" == "help" ]
then
  show_help
fi

if [ "$#" -eq 0 ]; then
  show_help
fi

SEARCH_FOR=$1
shift;

for ITEM in "$@"
do
  if [ "$SEARCH_FOR" == "$ITEM" ]
  then
    echo "YES"
    exit;
  fi
done

echo "NO"
Brad Parks
fonte
1

Supondo que a variável TARGET possa ser apenas 'binomial' ou 'regressão', o seguinte seria:

# Check for modeling types known to this script
if [ $( echo "${TARGET}" | egrep -c "^(binomial|regression)$" ) -eq 0 ]; then
    echo "This scoring program can only handle 'binomial' and 'regression' methods now." >&2
    usage
fi

Você pode adicionar mais strings à lista, separando-as com um | caractere (pipe).

A vantagem de usar o egrep é que você pode facilmente adicionar insensibilidade a maiúsculas e minúsculas (-i) ou verificar cenários mais complexos com uma expressão regular.

Tagar
fonte
1

Esta é quase a sua proposta original, mas quase uma linha. Não é tão complicado quanto outras respostas válidas, e não depende das versões do bash (pode funcionar com os antigos).

OK=0 ; MP_FLAVOURS="vanilla lemon hazelnut straciatella"
for FLAV in $MP_FLAVOURS ; do [ $FLAV == $FLAVOR ] && { OK=1 ; break; } ; done
[ $OK -eq 0 ] && { echo "$FLAVOR not a valid value ($MP_FLAVOURS)" ; exit 1 ; }

Acho que minha proposta ainda pode ser melhorada, tanto em tamanho quanto em estilo.

carnicer
fonte
0

Se não demorar muito; você pode apenas colocá-los entre igualdade ao longo de uma comparação lógica OU dessa forma.

if [ $ITEM == "item1" -o $ITEM == "item2" -o $ITEM == "item3" ]; then
    echo In the list
fi 

Eu tive esse problema exato e, embora o exposto seja feio, é mais óbvio o que está acontecendo do que as outras soluções generalizadas.

Kelly Trinh
fonte