Verifique se uma matriz Bash contém um valor

443

No Bash, qual é a maneira mais simples de testar se uma matriz contém um determinado valor?

Edit : Com a ajuda das respostas e dos comentários, após alguns testes, vim com isso:

function contains() {
    local n=$#
    local value=${!n}
    for ((i=1;i < $#;i++)) {
        if [ "${!i}" == "${value}" ]; then
            echo "y"
            return 0
        fi
    }
    echo "n"
    return 1
}

A=("one" "two" "three four")
if [ $(contains "${A[@]}" "one") == "y" ]; then
    echo "contains one"
fi
if [ $(contains "${A[@]}" "three") == "y" ]; then
    echo "contains three"
fi

Não tenho certeza se é a melhor solução, mas parece funcionar.

Paolo Tedesco
fonte

Respostas:

457

Essa abordagem tem a vantagem de não precisar fazer um loop sobre todos os elementos (pelo menos não explicitamente). Mas como array_to_string_internal()no array.c ainda faz um loop sobre os elementos do array e os concatena em uma string, provavelmente não é mais eficiente do que as soluções de loop propostas, mas é mais legível.

if [[ " ${array[@]} " =~ " ${value} " ]]; then
    # whatever you want to do when array contains value
fi

if [[ ! " ${array[@]} " =~ " ${value} " ]]; then
    # whatever you want to do when array doesn't contain value
fi

Observe que nos casos em que o valor que você está procurando é uma das palavras em um elemento da matriz com espaços, ele fornecerá falsos positivos. Por exemplo

array=("Jack Brown")
value="Jack"

O regex verá "Jack" como estando na matriz, mesmo que não esteja. Portanto, você precisará alterar IFSos caracteres separadores em sua regex, se ainda quiser usar esta solução, como esta

IFS=$'\t'
array=("Jack Brown\tJack Smith")
unset IFS
value="Jack"

if [[ "\t${array[@]}\t" =~ "\t${value}\t" ]]; then
    echo "true"
else
    echo "false"
fi

Isso imprimirá "false".

Obviamente, isso também pode ser usado como uma declaração de teste, permitindo que seja expresso como uma linha

[[ " ${array[@]} " =~ " ${value} " ]] && echo "true" || echo "false"
Keegan
fonte
1
Adicionei um espaço no início da primeira correspondência de valor de expressão regular, para que correspondesse apenas à palavra, e não a algo que termina na palavra. Funciona bem. No entanto, não entendo por que você usa a segunda condição, o primeiro não funcionaria bem sozinho?
JStrahl
1
@AwQiruiGuo Não tenho certeza se estou seguindo. Você está falando de matrizes com literais em dólares? Nesse caso, apenas escape os dólares no valor com o qual você está correspondendo com as barras invertidas.
21815 Keegan
10
Oneliner: [[ " ${branches[@]} " =~ " ${value} " ]] && echo "YES" || echo "NO";
ericson.cepeda
3
A Shellcheck reclama dessa solução, SC2199 e SC2076. Não consegui corrigir os avisos sem interromper a funcionalidade. Alguma idéia sobre isso, além de desativar a verificação de shell para essa linha?
Ali Essam
4
O SC2076 é fácil de corrigir, basta remover as aspas duplas no arquivoif . Não acho que haja uma maneira de evitar o SC2199 com essa abordagem. Você precisaria fazer um loop explícito na matriz, como mostrado em algumas das outras soluções, ou ignorar o aviso.
1619 Keegan
388

Abaixo está uma pequena função para conseguir isso. A cadeia de pesquisa é o primeiro argumento e o restante são os elementos da matriz:

containsElement () {
  local e match="$1"
  shift
  for e; do [[ "$e" == "$match" ]] && return 0; done
  return 1
}

Uma execução de teste dessa função pode se parecer com:

$ array=("something to search for" "a string" "test2000")
$ containsElement "a string" "${array[@]}"
$ echo $?
0
$ containsElement "blaha" "${array[@]}"
$ echo $?
1
patrik
fonte
5
Funciona bem! Eu só tenho que lembrar de passar a matriz como com citações: "${array[@]}". Caso contrário, os elementos que contêm espaços quebrarão a funcionalidade.
Jul
26
Agradável. Eu chamaria elementIn () porque verifica se o primeiro argumento está contido no segundo. containsElements () parece que a matriz iria primeiro. Para iniciantes como eu, um exemplo de como usar uma função que não escreve para stdout em uma declaração "if" ajudaria: if elementIn "$table" "${skip_tables[@]}" ; then echo skipping table: ${table}; fi; Obrigado pela sua ajuda!
precisa saber é o seguinte
5
@Bluz a construção && é um operador booleano AND. O uso de operadores booleanos cria uma instrução booleana. A lógica booleana diz que a instrução inteira só pode ser verdadeira se as instruções antes e depois do && forem avaliadas como verdadeiras. Isso é usado como um atalho no instante e no bloqueio. O teste é avaliado e, se falso, não há necessidade de avaliar o retorno, pois é irrelevante para toda a instrução, uma vez que o teste falhou e, portanto, não é executado. Se o teste for bem-sucedido, o sucesso da instrução booleana exige que o resultado do retorno seja determinado para que o código seja executado.
peteches
4
@ James por convenção, o código de sucesso no bash é "0" e o erro é tudo> = 1. É por isso que retorna 0 em caso de sucesso. :)
tftd
11
@Stelios shiftdesloca a lista de argumentos em 1 para a esquerda (descartando o primeiro argumento) e forsem uma initeração implícita sobre a lista de argumentos.
Christian
58
$ myarray=(one two three)
$ case "${myarray[@]}" in  *"two"*) echo "found" ;; esac
found
ghostdog74
fonte
69
Observe que isso não itera sobre cada elemento da matriz separadamente ... em vez disso, simplesmente concatena a matriz e corresponde a "dois" como uma substring. Isso pode causar comportamento indesejável se alguém estiver testando se a palavra exata "dois" é um elemento da matriz.
MartyMacGyver
Eu pensei que isso iria funcionar para mim na comparação de tipos de arquivo, mas descobri que, à medida que os contadores aumentavam, ele estava contando muitos valores ... boo!
Mike Q
17
errado! Razão: case "${myarray[@]}" in *"t"*) echo "found" ;; esacoutputs:found
Sergej Jevsejev 02/08
@MartyMacGyver, você poderia ter o olhar no meu Além de esta resposta stackoverflow.com/a/52414872/1619950
Aleksandr Podkutin
45
for i in "${array[@]}"
do
    if [ "$i" -eq "$yourValue" ] ; then
        echo "Found"
    fi
done

Para strings:

for i in "${array[@]}"
do
    if [ "$i" == "$yourValue" ] ; then
        echo "Found"
    fi
done
Scott
fonte
Dito isto, você pode usar um índice para o laço e evitar ser morto quando um elemento da matriz contém IFS: for ((i = 0; i <$ {# array [@]}; i ++))
mkb
@ Matt: Você deve ter cuidado ao usar, ${#}pois o Bash suporta matrizes esparsas.
Pausado até novo aviso.
@ Paolo, se sua matriz contiver um espaço, basta compará-la como uma string. um espaço também é uma string.
Scott
@ Paolo: Você pode transformar isso em uma função, mas as matrizes não podem ser passadas como argumentos, portanto você terá que tratá-la como uma global.
Pausado até novo aviso.
Dennis está certo. No manual de referência do bash: "Se a palavra estiver entre aspas duplas, ... $ {name [@]} expande cada elemento do nome para uma palavra separada"
mkb
37

Solução de uma linha

printf '%s\n' ${myarray[@]} | grep -P '^mypattern$'

Explicação

A printfinstrução imprime cada elemento da matriz em uma linha separada.

A grepdeclaração usa os caracteres especiais ^e $encontra uma linha que contém exatamente o padrão fornecido como mypattern(nem mais, nem menos).


Uso

Para colocar isso em uma if ... thendeclaração:

if printf '%s\n' ${myarray[@]} | grep -q -P '^mypattern$'; then
    # ...
fi

Eu adicionei um -qsinalizador à grepexpressão para que ela não imprima correspondências; tratará apenas a existência de uma correspondência como "verdadeira".

JellicleCat
fonte
Ótima solução! No GNU grep, há também "--line-regexp" que poderia substituir "-P" e ^ e $ no padrão: printf '% s \ n' $ {myarray [@]} | grep -q --line-regexp 'mypattern'
presto8
19

Se você precisar de desempenho, não deseja percorrer toda a matriz toda vez que pesquisar.

Nesse caso, você pode criar uma matriz associativa (tabela de hash ou dicionário) que represente um índice dessa matriz. Ou seja, ele mapeia cada elemento da matriz em seu índice na matriz:

make_index () {
  local index_name=$1
  shift
  local -a value_array=("$@")
  local i
  # -A means associative array, -g means create a global variable:
  declare -g -A ${index_name}
  for i in "${!value_array[@]}"; do
    eval ${index_name}["${value_array[$i]}"]=$i
  done
}

Então você pode usá-lo assim:

myarray=('a a' 'b b' 'c c')
make_index myarray_index "${myarray[@]}"

E teste a associação da seguinte forma:

member="b b"
# the "|| echo NOT FOUND" below is needed if you're using "set -e"
test "${myarray_index[$member]}" && echo FOUND || echo NOT FOUND

Ou também:

if [ "${myarray_index[$member]}" ]; then 
  echo FOUND
fi

Observe que esta solução faz a coisa certa, mesmo que haja espaços no valor testado ou nos valores da matriz.

Como bônus, você também obtém o índice do valor dentro da matriz com:

echo "<< ${myarray_index[$member]} >> is the index of $member"
LeoRochael
fonte
+1 para a ideia de que você deve usar uma matriz associativa. Eu acho que o código para make_indexé um pouco mais artificial devido ao indireção; você poderia ter usado um nome fixo de matriz com um código muito mais simples.
Musiphil
17

Eu normalmente apenas uso:

inarray=$(echo ${haystack[@]} | grep -o "needle" | wc -w)

um valor diferente de zero indica que uma correspondência foi encontrada.

Sean DiSanti
fonte
É verdade que esta é definitivamente a solução mais fácil - deve ser uma resposta marcada na minha opinião. Pelo menos tenho o meu voto! [:
ToVine 24/04/2015
2
Isso não funciona para agulhas semelhantes. Por exemplo,haystack=(needle1 needle2); echo ${haystack[@]} | grep -o "needle" | wc -w
Keegan
1
Muito verdadeiro. juntar-se a um delimitador não presente em nenhum elemento e adicioná-lo à agulha ajudaria nisso. Talvez algo como ... (não testado)inarray=$(printf ",%s" "${haystack[@]}") | grep -o ",needle" | wc -w)
Sean DiSanti
2
O uso de grep -x evitaria falsos positivos: inarray=$(printf ",%s" "${haystack[@]}") | grep -x "needle" | wc -l
jesjimher
Talvez simplesmente inarray=$(echo " ${haystack[@]}" | grep -o " needle" | wc -w)como causas -x grep para tentar corresponder toda a cadeia de entrada
MI Wright
17

Outro liner sem função:

(for e in "${array[@]}"; do [[ "$e" == "searched_item" ]] && exit 0; done) && echo "found" || echo "not found"

Obrigado @ Qwerty pelo aviso sobre os espaços!

função correspondente:

find_in_array() {
  local word=$1
  shift
  for e in "$@"; do [[ "$e" == "$word" ]] && return 0; done
  return 1
}

exemplo:

some_words=( these are some words )
find_in_array word "${some_words[@]}" || echo "expected missing! since words != word"
estani
fonte
1
Por que precisamos de um subshell aqui?
codeforester
1
@ codeforester isso é antigo ... mas como foi escrito, você precisa dele para se livrar dele, é o que exit 0faz (pára o mais rápido possível, se encontrado).
Estani
O final de um liner deve estar em || echo not foundvez de || not foundou o shell tentará executar um comando com o nome de not com argumento encontrado se o valor solicitado não estiver na matriz.
Zoke
11
containsElement () { for e in "${@:2}"; do [[ "$e" = "$1" ]] && return 0; done; return 1; }

Agora lida com matrizes vazias corretamente.

Yann
fonte
Como isso é diferente da resposta de @ patrik? A única diferença que vejo é "$e" = "$1"(em vez de "$e" == "$1") que parece um bug.
CivFan
1
Não é. @ patrik mesclou meu comentário em sua resposta original naquela época (patch 4). Nota: "e" == "$1"é sintaticamente mais claro.
Yann
@CivFan Na sua forma atual, isso é mais curto que o da resposta do patrik, devido ao elegante $ {@: 2} e à auto-documentação de $ 1. Eu acrescentaria que não é necessário citar em [[]].
Hontvári Levente
9

Aqui está uma pequena contribuição:

array=(word "two words" words)  
search_string="two"  
match=$(echo "${array[@]:0}" | grep -o $search_string)  
[[ ! -z $match ]] && echo "found !"  

Nota: dessa maneira não distingue o caso "duas palavras", mas isso não é necessário na pergunta.

hornetbzz
fonte
Este me ajudou muito. Obrigado!
Ed Manet
A pergunta não disse explicitamente que você tinha que dar a resposta correta, mas acho que está implícito na pergunta ... A matriz não contém o valor "dois".
Tetsujin
O acima relatará uma correspondência para 'rd'.
Noel Yap
6

Se você deseja fazer um teste rápido e sujo para ver se vale a pena repetir toda a matriz para obter uma correspondência precisa, o Bash pode tratar matrizes como escalares. Teste para uma correspondência no escalar, se nenhum, pular o loop economiza tempo. Obviamente, você pode obter falsos positivos.

array=(word "two words" words)
if [[ ${array[@]} =~ words ]]
then
    echo "Checking"
    for element in "${array[@]}"
    do
        if [[ $element == "words" ]]
        then
            echo "Match"
        fi
    done
fi

Isso exibirá "Checking" e "Match". Com array=(word "two words" something)ele, somente será emitida "Verificação". Com array=(word "two widgets" something)não haverá saída.

Pausado até novo aviso.
fonte
Por que não substituir apenas wordspor uma regex ^words$que corresponda apenas a toda a cadeia, o que elimina completamente a necessidade de verificar cada item individualmente?
precisa saber é o seguinte
@DejayClayton: Porque pattern='^words$'; if [[ ${array[@]} =~ $pattern ]]nunca corresponderá, pois está verificando toda a matriz de uma vez como se fosse um escalar. As verificações individuais em minha resposta devem ser feitas apenas se houver um motivo para prosseguir com base na correspondência aproximada.
Pausado até novo aviso.
Ah, eu vejo o que você está tentando fazer. Propus uma resposta variante mais eficiente e segura.
Dejay Clayton 13/09/19
6

Isso está funcionando para mim:

# traditional system call return values-- used in an `if`, this will be true when returning 0. Very Odd.
contains () {
    # odd syntax here for passing array parameters: http://stackoverflow.com/questions/8082947/how-to-pass-an-array-to-a-bash-function
    local list=$1[@]
    local elem=$2

    # echo "list" ${!list}
    # echo "elem" $elem

    for i in "${!list}"
    do
        # echo "Checking to see if" "$i" "is the same as" "${elem}"
        if [ "$i" == "${elem}" ] ; then
            # echo "$i" "was the same as" "${elem}"
            return 0
        fi
    done

    # echo "Could not find element"
    return 1
}

Chamada de exemplo:

arr=("abc" "xyz" "123")
if contains arr "abcx"; then
    echo "Yes"
else
    echo "No"
fi
Chris Prince
fonte
5
a=(b c d)

if printf '%s\0' "${a[@]}" | grep -Fqxz c
then
  echo 'array “a” contains value “c”'
fi

Se preferir, você pode usar opções longas equivalentes:

--fixed-strings --quiet --line-regexp --null-data
Steven Penny
fonte
1
Isso não funciona com o BSD-grep no Mac, pois não há --null-data. :(
Will
4

Tomando emprestado a resposta de Dennis Williamson , a solução a seguir combina matrizes, citações seguras de shell e expressões regulares para evitar a necessidade de: iterar sobre loops; usando tubos ou outros subprocessos; ou usando utilitários que não sejam do bash.

declare -a array=('hello, stack' one 'two words' words last)
printf -v array_str -- ',,%q' "${array[@]}"

if [[ "${array_str},," =~ ,,words,, ]]
then
   echo 'Matches'
else
   echo "Doesn't match"
fi

O código acima funciona usando expressões regulares do Bash para corresponder a uma versão rigorosa do conteúdo da matriz. Há seis etapas importantes para garantir que a correspondência da expressão regular não possa ser enganada por combinações inteligentes de valores na matriz:

  1. Construa a string de comparação usando a printfcitação de shell incorporada do Bash %q,. A citação de shell garantirá que caracteres especiais se tornem "seguros para shell" ao serem escapados com barra invertida\ .
  2. Escolha um caractere especial para servir como um delimitador de valor. O delimitador TEM QUE ser um dos caracteres especiais que serão escapados ao usar %q; essa é a única maneira de garantir que os valores dentro da matriz não possam ser construídos de maneiras inteligentes para enganar a correspondência de expressão regular. Eu escolho vírgula, porque esse personagem é o mais seguro quando avaliado ou usado de maneira inesperada.
  3. Combine todos os elementos da matriz em uma única sequência, usando duas instâncias do caractere especial para servir como delimitador. Usando vírgula como exemplo, usei ,,%qcomo argumento para printf. Isso é importante porque duas instâncias do caractere especial só podem aparecer próximas uma da outra quando aparecem como delimitador; todas as outras instâncias do caractere especial serão escapadas.
  4. Anexe duas instâncias à direita do delimitador à string, para permitir correspondências contra o último elemento da matriz. Assim, em vez de comparar contra ${array_str}, compare contra ${array_str},,.
  5. Se a sequência de destino que você estiver procurando for fornecida por uma variável do usuário, você deverá escapar de todas as instâncias do caractere especial com uma barra invertida. Caso contrário, a correspondência de expressão regular fica vulnerável a ser enganada por elementos de matriz criados de maneira inteligente.
  6. Execute uma correspondência de expressão regular do Bash na string.
Dejay Clayton
fonte
Muito esperto. Percebo que a maioria dos problemas em potencial é evitada, mas eu gostaria de testar para ver se existem casos de esquina. Além disso, eu gostaria de ver um exemplo do ponto de manipulação 5. Algo como printf -v pattern ',,%q,,' "$user_input"; if [[ "${array_str},," =~ $pattern ]]talvez.
Pausado até novo aviso.
case "$(printf ,,%q "${haystack[@]}"),," in (*"$(printf ,,%q,, "$needle")"*) true;; (*) false;; esac
Tino
3

Uma pequena adição à resposta de @ ghostdog74 sobre o uso da caselógica para verificar se a matriz contém um valor específico:

myarray=(one two three)
word=two
case "${myarray[@]}" in  ("$word "*|*" $word "*|*" $word") echo "found" ;; esac

Ou com a extglobopção ativada, você pode fazer o seguinte:

myarray=(one two three)
word=two
shopt -s extglob
case "${myarray[@]}" in ?(*" ")"$word"?(" "*)) echo "found" ;; esac

Também podemos fazê-lo com a ifdeclaração:

myarray=(one two three)
word=two
if [[ $(printf "_[%s]_" "${myarray[@]}") =~ .*_\[$word\]_.* ]]; then echo "found"; fi
Aleksandr Podkutin
fonte
2

dado:

array=("something to search for" "a string" "test2000")
elem="a string"

depois, uma simples verificação de:

if c=$'\x1E' && p="${c}${elem} ${c}" && [[ ! "${array[@]/#/${c}} ${c}" =~ $p ]]; then
  echo "$elem exists in array"
fi

Onde

c is element separator
p is regex pattern

(O motivo para atribuir p separadamente, em vez de usar a expressão diretamente dentro de [[]] é manter a compatibilidade com o bash 4)

Beorn Harris
fonte
adoro o uso da palavra "simples" aqui ... 😂
Christian
2

Combinando algumas das idéias apresentadas aqui, você pode criar uma declaração elegante, sem loops, que corresponda exatamente às palavras .

$find="myword"
$array=(value1 value2 myword)
if [[ ! -z $(printf '%s\n' "${array[@]}" | grep -w $find) ]]; then
  echo "Array contains myword";
fi

Isso não será acionado wordou val, apenas correspondências de palavras inteiras. Ele será interrompido se cada valor da matriz contiver várias palavras.

Ecker00
fonte
1

Geralmente, escrevo esse tipo de utilitário para operar com o nome da variável, e não com o valor da variável, principalmente porque o bash não pode passar variáveis ​​por referência.

Aqui está uma versão que funciona com o nome da matriz:

function array_contains # array value
{
    [[ -n "$1" && -n "$2" ]] || {
        echo "usage: array_contains <array> <value>"
        echo "Returns 0 if array contains value, 1 otherwise"
        return 2
    }

    eval 'local values=("${'$1'[@]}")'

    local element
    for element in "${values[@]}"; do
        [[ "$element" == "$2" ]] && return 0
    done
    return 1
}

Com isso, o exemplo da pergunta se torna:

array_contains A "one" && echo "contains one"

etc.

Barry Kelly
fonte
Alguém pode postar um exemplo disso usado em um if, especialmente como você passa na matriz. Estou tentando verificar se um argumento para o script foi passado tratando os parâmetros como uma matriz, mas ele não quer funcionar. params = ("$ @") cheque = array_contains $ {params} 'SKIPDIRCHECK' if [[$ {check} == 1]]; então .... Mas ao executar o script com 'asas' como argumento, ele continua dizendo asas: comando não encontrado. : /
Steve Childs
1

Usando grepeprintf

Formate cada membro da matriz em uma nova linha, depois grepas linhas.

if printf '%s\n' "${array[@]}" | grep -x -q "search string"; then echo true; else echo false; fi
exemplo:
$ array=("word", "two words")
$ if printf '%s\n' "${array[@]}" | grep -x -q "two words"; then echo true; else echo false; fi
true

Observe que isso não tem problemas com delimitadores e espaços.

Qwerty
fonte
1

Verificação em uma linha sem 'grep' e loops

if ( dlm=$'\x1F' ; IFS="$dlm" ; [[ "$dlm${array[*]}$dlm" == *"$dlm${item}$dlm"* ]] ) ; then
  echo "array contains '$item'"
else
  echo "array does not contain '$item'"
fi

Essa abordagem não usa utilitários externos como grepnem loops.

O que acontece aqui é:

  • usamos um combinador de substring curinga para encontrar nosso item na matriz concatenada em uma string;
  • eliminamos possíveis falsos positivos colocando nosso item de pesquisa entre um par de delimitadores;
  • usamos um caractere não imprimível como delimitador, para estar do lado seguro;
  • alcançamos que nosso delimitador também seja usado para concatenação de matriz pela substituição temporária do IFSvalor da variável;
  • tornamos essa IFSsubstituição de valor temporária avaliando nossa expressão condicional em uma subcasca (dentro de um par de parênteses)
Sergey Ushakov
fonte
Eliminar dlm. Use o IFS diretamente.
Robin A. Meade
Esta é a melhor resposta. Eu gostei muito, escrevi uma função usando essa técnica .
Robin A. Meade
1

Usando expansão de parâmetro:

$ {parameter: + word} Se o parâmetro for nulo ou não definido, nada será substituído; caso contrário, a expansão da palavra será substituída.

declare -A myarray
myarray[hello]="world"

for i in hello goodbye 123
do
  if [ ${myarray[$i]:+_} ]
  then
    echo ${!myarray[$i]} ${myarray[$i]} 
  else
    printf "there is no %s\n" $i
  fi
done
Gabriel Laden
fonte
${myarray[hello]:+_}funciona muito bem para matrizes associativas, mas não para matrizes indexadas comuns. A questão é encontrar um valor em um aray, não verificar se a chave de uma matriz associativa existe.
Eric
0

Depois de ter respondido, li outra resposta que particularmente gostei, mas que foi falho e prejudicada. Eu me inspirei e aqui estão duas novas abordagens que considero viáveis.

array=("word" "two words") # let's look for "two words"

usando grepeprintf :

(printf '%s\n' "${array[@]}" | grep -x -q "two words") && <run_your_if_found_command_here>

usando for:

(for e in "${array[@]}"; do [[ "$e" == "two words" ]] && exit 0; done; exit 1) && <run_your_if_found_command_here>

Para resultados not_found, adicione || <run_your_if_notfound_command_here>

Qwerty
fonte
0

Aqui está a minha opinião sobre isso.

Prefiro não usar o loop for do bash se puder evitá-lo, pois isso leva tempo para ser executado. Se algo tiver que ser repetido, seja algo que foi escrito em uma linguagem de nível inferior ao script de shell.

function array_contains { # arrayname value
  local -A _arr=()
  local IFS=
  eval _arr=( $(eval printf '[%q]="1"\ ' "\${$1[@]}") )
  return $(( 1 - 0${_arr[$2]} ))
}

Isso funciona criando uma matriz associativa temporária _arr, cujos índices são derivados dos valores da matriz de entrada. (Observe que matrizes associativas estão disponíveis no bash 4 e acima, portanto, essa função não funcionará nas versões anteriores do bash.) Definimos $IFSpara evitar a divisão de palavras no espaço em branco.

A função não contém loops explícitos, embora as etapas internas do bash na matriz de entrada sejam preenchidas printf. O formato printf é usado %qpara garantir que os dados de entrada sejam escapados, de forma que possam ser usados ​​com segurança como chaves de matriz.

$ a=("one two" three four)
$ array_contains a three && echo BOOYA
BOOYA
$ array_contains a two && echo FAIL
$

Observe que tudo o que essa função usa é incorporado ao bash, portanto, não há canais externos arrastando você para baixo, mesmo na expansão de comando.

E se você não gosta de usar eval... bem, você é livre para usar outra abordagem. :-)

ghoti
fonte
E se a matriz contiver colchetes?
911717
@gniourf_gniourf - parece estar bem se colchetes são balanceados, mas posso ver que é um problema se sua matriz incluir valores com colchetes desequilibrados. Nesse caso, eu chamaria a evalinstrução no final da resposta. :)
ghoti 09/02
Não é que eu não goste eval(não tenho nada contra, ao contrário da maioria das pessoas que chora evalé má, principalmente sem entender o que há de ruim nisso). Só que seu comando está quebrado. Talvez em %qvez de %sseria melhor.
Gnourf_gniourf
1
@gniourf_gniourf: Eu só quis dizer a parte "outra abordagem" (e estou totalmente com você eval, obviamente), mas você está absolutamente certo, %qparece ajudar, sem quebrar nada que eu possa ver. (Eu não sabia que% q escaparia de colchetes também.) Outro problema que vi e corrigi foi em relação a espaços em branco. Com a=(one "two " three), semelhante à questão de Keegan: não apenas array_contains a "two "obteve um falso negativo, mas também array_contains a twoum falso positivo. Fácil o suficiente para corrigir, definindo IFS.
ghoti
Em relação aos espaços em branco, não é porque faltam aspas? também quebra com caracteres glob. Eu acho que você quer isso:, eval _arr=( $(eval printf '[%q]="1"\ ' "\"\${$1[@]}\"") )e você pode abandonar o local IFS=. Ainda há um problema com campos vazios na matriz, pois o Bash se recusará a criar uma chave vazia em uma matriz associativa. Uma maneira rápida e rápida de corrigi-lo é anexar um personagem falso, digamos x: eval _arr=( $(eval printf '[x%q]="1"\ ' "\"\${$1[@]}\"") )e return $(( 1 - 0${_arr[x$2]} )).
Gnourf_gniourf
-1

Minha versão da técnica de expressões regulares já foi sugerida:

values=(foo bar)
requestedValue=bar

requestedValue=${requestedValue##[[:space:]]}
requestedValue=${requestedValue%%[[:space:]]}
[[ "${values[@]/#/X-}" =~ "X-${requestedValue}" ]] || echo "Unsupported value"

O que está acontecendo aqui é que você está expandindo toda a matriz de valores suportados em palavras e anexando uma sequência específica, "X-", neste caso, a cada um deles, e fazendo o mesmo com o valor solicitado. Se este realmente estiver contido na matriz, a sequência resultante corresponderá, no máximo, a um dos tokens resultantes, ou nenhum ao contrário. Neste último caso, o || O operador é acionado e você sabe que está lidando com um valor não suportado. Antes disso, o valor solicitado é retirado de todos os espaços em branco iniciais e finais através da manipulação padrão de strings de shell.

É limpo e elegante, acredito, embora não tenha muita certeza do desempenho, se sua matriz de valores suportados for particularmente grande.

jmpp
fonte
-1

Aqui está a minha opinião sobre este problema. Aqui está a versão curta:

function arrayContains() {
        local haystack=${!1}
        local needle="$2"
        printf "%s\n" ${haystack[@]} | grep -q "^$needle$"
}

E a versão longa, que eu acho que é muito mais fácil para os olhos.

# With added utility function.
function arrayToLines() {
        local array=${!1}
        printf "%s\n" ${array[@]}
}

function arrayContains() {
        local haystack=${!1}
        local needle="$2"
        arrayToLines haystack[@] | grep -q "^$needle$"
}

Exemplos:

test_arr=("hello" "world")
arrayContains test_arr[@] hello; # True
arrayContains test_arr[@] world; # True
arrayContains test_arr[@] "hello world"; # False
arrayContains test_arr[@] "hell"; # False
arrayContains test_arr[@] ""; # False
Robert
fonte
Eu não uso o bash há tanto tempo agora que tenho dificuldade em entender as respostas, ou mesmo o que eu escrevi :) Não acredito que essa pergunta ainda esteja em atividade depois de todo esse tempo :)
Paolo Tedesco
Que tal test_arr=("hello" "world" "two words")?
Qwerty
-1

Tive o caso de verificar se havia um ID em uma lista de IDs gerados por outro script / comando. Para mim trabalhou o seguinte:

# the ID I was looking for
ID=1

# somehow generated list of IDs
LIST=$( <some script that generates lines with IDs> )
# list is curiously concatenated with a single space character
LIST=" $LIST "

# grep for exact match, boundaries are marked as space
# would therefore not reliably work for values containing a space
# return the count with "-c"
ISIN=$(echo $LIST | grep -F " $ID " -c)

# do your check (e. g. 0 for nothing found, everything greater than 0 means found)
if [ ISIN -eq 0 ]; then
    echo "not found"
fi
# etc.

Você também pode encurtar / compactar assim:

if [ $(echo " $( <script call> ) " | grep -F " $ID " -c) -eq 0 ]; then
    echo "not found"
fi

No meu caso, eu estava executando o jq para filtrar algum JSON para obter uma lista de IDs e tive que verificar mais tarde se meu ID estava nessa lista e isso funcionou melhor para mim. Não funcionará para matrizes criadas manualmente do tipoLIST=("1" "2" "4") mas para com saída de script separada por nova linha.


PS .: não pude comentar uma resposta porque sou relativamente nova ...

E. Körner
fonte
-2

O código a seguir verifica se um determinado valor está na matriz e retorna seu deslocamento com base em zero:

A=("one" "two" "three four")
VALUE="two"

if [[ "$(declare -p A)" =~ '['([0-9]+)']="'$VALUE'"' ]];then
  echo "Found $VALUE at offset ${BASH_REMATCH[1]}"
else
  echo "Couldn't find $VALUE"
fi

A correspondência é feita com os valores completos, portanto, definir VALUE = "três" não corresponderia.

Sven Rieke
fonte
-2

Vale a pena investigar se você não quiser repetir:

#!/bin/bash
myarray=("one" "two" "three");
wanted="two"
if `echo ${myarray[@]/"$wanted"/"WAS_FOUND"} | grep -q "WAS_FOUND" ` ; then
 echo "Value was found"
fi
exit

Snippet adaptado de: http://www.thegeekstuff.com/2010/06/bash-array-tutorial/ Eu acho que é bem inteligente.

Edição: Você provavelmente poderia apenas fazer:

if `echo ${myarray[@]} | grep -q "$wanted"` ; then
echo "Value was found"
fi

Mas o último só funciona se a matriz contiver valores exclusivos. Procurar 1 em "143" dará falso positivo, metinks.

Sigg3.net
fonte
-2

Um pouco tarde, mas você pode usar isso:

#!/bin/bash
# isPicture.sh

FILE=$1
FNAME=$(basename "$FILE") # Filename, without directory
EXT="${FNAME##*.}" # Extension

FORMATS=(jpeg JPEG jpg JPG png PNG gif GIF svg SVG tiff TIFF)

NOEXT=( ${FORMATS[@]/$EXT} ) # Formats without the extension of the input file

# If it is a valid extension, then it should be removed from ${NOEXT},
#+making the lengths inequal.
if ! [ ${#NOEXT[@]} != ${#FORMATS[@]} ]; then
    echo "The extension '"$EXT"' is not a valid image extension."
    exit
fi
Coder-256
fonte
-2

Eu vim com este, que acaba funcionando apenas no zsh, mas acho que a abordagem geral é boa.

arr=( "hello world" "find me" "what?" )
if [[ "${arr[@]/#%find me/}" != "${arr[@]}" ]]; then
    echo "found!"
else
    echo "not found!"
fi

Você remove seu padrão de cada elemento apenas se ele começar ${arr[@]/#pattern/}ou terminar ${arr[@]/%pattern/}com ele. Essas duas substituições funcionam no bash, mas ambas ao mesmo tempo${arr[@]/#%pattern/} funcionam apenas no zsh.

Se a matriz modificada for igual ao original, ela não conterá o elemento

Editar:

Este funciona no bash:

 function contains () {
        local arr=(${@:2})
        local el=$1
        local marr=(${arr[@]/#$el/})
        [[ "${#arr[@]}" != "${#marr[@]}" ]]
    }

Após a substituição, ele compara o comprimento de ambas as matrizes. Obviamente, se a matriz contiver o elemento, a substituição a excluirá completamente e a contagem será diferente.

spelufo
fonte