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.
[[ " ${branches[@]} " =~ " ${value} " ]] && echo "YES" || echo "NO";
if
. 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.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:
Uma execução de teste dessa função pode se parecer com:
fonte
"${array[@]}"
. Caso contrário, os elementos que contêm espaços quebrarão a funcionalidade.if elementIn "$table" "${skip_tables[@]}" ; then echo skipping table: ${table}; fi;
Obrigado pela sua ajuda!shift
desloca a lista de argumentos em 1 para a esquerda (descartando o primeiro argumento) efor
sem umain
iteração implícita sobre a lista de argumentos.fonte
case "${myarray[@]}" in *"t"*) echo "found" ;; esac
outputs:found
Para strings:
fonte
${#}
pois o Bash suporta matrizes esparsas.Solução de uma linha
Explicação
A
printf
instrução imprime cada elemento da matriz em uma linha separada.A
grep
declaração usa os caracteres especiais^
e$
encontra uma linha que contém exatamente o padrão fornecido comomypattern
(nem mais, nem menos).Uso
Para colocar isso em uma
if ... then
declaração:Eu adicionei um
-q
sinalizador àgrep
expressão para que ela não imprima correspondências; tratará apenas a existência de uma correspondência como "verdadeira".fonte
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:
Então você pode usá-lo assim:
E teste a associação da seguinte forma:
Ou também:
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:
fonte
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.Eu normalmente apenas uso:
um valor diferente de zero indica que uma correspondência foi encontrada.
fonte
haystack=(needle1 needle2); echo ${haystack[@]} | grep -o "needle" | wc -w
inarray=$(printf ",%s" "${haystack[@]}") | grep -o ",needle" | wc -w)
inarray=$(printf ",%s" "${haystack[@]}") | grep -x "needle" | wc -l
inarray=$(echo " ${haystack[@]}" | grep -o " needle" | wc -w)
como causas -x grep para tentar corresponder toda a cadeia de entradaOutro liner sem função:
Obrigado @ Qwerty pelo aviso sobre os espaços!
função correspondente:
exemplo:
fonte
exit 0
faz (pára o mais rápido possível, se encontrado).|| echo not found
vez de|| not found
ou o shell tentará executar um comando com o nome de not com argumento encontrado se o valor solicitado não estiver na matriz.Agora lida com matrizes vazias corretamente.
fonte
"$e" = "$1"
(em vez de"$e" == "$1"
) que parece um bug."e" == "$1"
é sintaticamente mais claro.Aqui está uma pequena contribuição:
Nota: dessa maneira não distingue o caso "duas palavras", mas isso não é necessário na pergunta.
fonte
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.
Isso exibirá "Checking" e "Match". Com
array=(word "two words" something)
ele, somente será emitida "Verificação". Comarray=(word "two widgets" something)
não haverá saída.fonte
words
por uma regex^words$
que corresponda apenas a toda a cadeia, o que elimina completamente a necessidade de verificar cada item individualmente?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.Isso está funcionando para mim:
Chamada de exemplo:
fonte
Se preferir, você pode usar opções longas equivalentes:
fonte
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.
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:
printf
citaçã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\
.%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.,,%q
como argumento paraprintf
. 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.${array_str}
, compare contra${array_str},,
.fonte
printf -v pattern ',,%q,,' "$user_input"; if [[ "${array_str},," =~ $pattern ]]
talvez.case "$(printf ,,%q "${haystack[@]}"),," in (*"$(printf ,,%q,, "$needle")"*) true;; (*) false;; esac
Uma pequena adição à resposta de @ ghostdog74 sobre o uso da
case
lógica para verificar se a matriz contém um valor específico:Ou com a
extglob
opção ativada, você pode fazer o seguinte:Também podemos fazê-lo com a
if
declaração:fonte
dado:
depois, uma simples verificação de:
Onde
(O motivo para atribuir p separadamente, em vez de usar a expressão diretamente dentro de [[]] é manter a compatibilidade com o bash 4)
fonte
Combinando algumas das idéias apresentadas aqui, você pode criar uma declaração elegante, sem loops, que corresponda exatamente às palavras .
Isso não será acionado
word
ouval
, apenas correspondências de palavras inteiras. Ele será interrompido se cada valor da matriz contiver várias palavras.fonte
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:
Com isso, o exemplo da pergunta se torna:
etc.
fonte
Usando
grep
eprintf
Formate cada membro da matriz em uma nova linha, depois
exemplo:grep
as linhas.Observe que isso não tem problemas com delimitadores e espaços.
fonte
Verificação em uma linha sem 'grep' e loops
Essa abordagem não usa utilitários externos como
grep
nem loops.O que acontece aqui é:
IFS
valor da variável;IFS
substituição de valor temporária avaliando nossa expressão condicional em uma subcasca (dentro de um par de parênteses)fonte
Usando expansão de parâmetro:
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.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.
usando
grep
eprintf
:usando
for
:Para resultados not_found, adicione
|| <run_your_if_notfound_command_here>
fonte
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.
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$IFS
para 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%q
para garantir que os dados de entrada sejam escapados, de forma que possam ser usados com segurança como chaves de matriz.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. :-)fonte
eval
instrução no final da resposta. :)eval
(não tenho nada contra, ao contrário da maioria das pessoas que choraeval
é má, principalmente sem entender o que há de ruim nisso). Só que seu comando está quebrado. Talvez em%q
vez de%s
seria melhor.eval
, obviamente), mas você está absolutamente certo,%q
parece 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. Coma=(one "two " three)
, semelhante à questão de Keegan: não apenasarray_contains a "two "
obteve um falso negativo, mas tambémarray_contains a two
um falso positivo. Fácil o suficiente para corrigir, definindoIFS
.eval _arr=( $(eval printf '[%q]="1"\ ' "\"\${$1[@]}\"") )
e você pode abandonar olocal 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, digamosx
:eval _arr=( $(eval printf '[x%q]="1"\ ' "\"\${$1[@]}\"") )
ereturn $(( 1 - 0${_arr[x$2]} ))
.Minha versão da técnica de expressões regulares já foi sugerida:
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.
fonte
Aqui está a minha opinião sobre este problema. Aqui está a versão curta:
E a versão longa, que eu acho que é muito mais fácil para os olhos.
Exemplos:
fonte
test_arr=("hello" "world" "two words")
?Tive o caso de verificar se havia um ID em uma lista de IDs gerados por outro script / comando. Para mim trabalhou o seguinte:
Você também pode encurtar / compactar assim:
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 tipo
LIST=("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 ...
fonte
O código a seguir verifica se um determinado valor está na matriz e retorna seu deslocamento com base em zero:
A correspondência é feita com os valores completos, portanto, definir VALUE = "três" não corresponderia.
fonte
Vale a pena investigar se você não quiser repetir:
Snippet adaptado de: http://www.thegeekstuff.com/2010/06/bash-array-tutorial/ Eu acho que é bem inteligente.
Edição: Você provavelmente poderia apenas fazer:
Mas o último só funciona se a matriz contiver valores exclusivos. Procurar 1 em "143" dará falso positivo, metinks.
fonte
Um pouco tarde, mas você pode usar isso:
fonte
Eu vim com este, que acaba funcionando apenas no zsh, mas acho que a abordagem geral é boa.
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:
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.
fonte