Teste se o elemento está na matriz no bash

17

Existe uma boa maneira de verificar se uma matriz tem um elemento no bash (melhor do que repetir)?

Como alternativa, existe outra maneira de verificar se um número ou sequência é igual a um conjunto de constantes predefinidas?

Tgr
fonte

Respostas:

24

No Bash 4, você pode usar matrizes associativas:

# set up array of constants
declare -A array
for constant in foo bar baz
do
    array[$constant]=1
done

# test for existence
test1="bar"
test2="xyzzy"

if [[ ${array[$test1]} ]]; then echo "Exists"; fi    # Exists
if [[ ${array[$test2]} ]]; then echo "Exists"; fi    # doesn't

Para configurar a matriz inicialmente, você também pode fazer atribuições diretas:

array[foo]=1
array[bar]=1
# etc.

ou desta maneira:

array=([foo]=1 [bar]=1 [baz]=1)
Pausado até novo aviso.
fonte
Na verdade, o teste [[]] não funciona no caso de o valor estar vazio. Por exemplo, "array ['test'] = ''". Nesse caso, a chave 'test' existe e você pode vê-la listada com $ {! Array [@]}, mas "[[$ {array ['test']}]]; ecoa $?" ecos 1, não 0. #
haridsv 6/06/11
1
${array[$test1]}é simples, mas tem um problema: não funcionará se você usar set -useus scripts (o que é recomendado), pois você obteria "variável não acoplada".
tokland
@ tokland: Quem o recomenda? Eu certamente não.
Pausado até novo aviso.
@DennisWilliamson: Ok, algumas pessoas recomendam, mas acho que seria bom ter uma solução que funcione independentemente do valor dessas bandeiras.
tokland
10

É uma questão antiga, mas eu acho que é a solução mais simples ainda não apareceu: test ${array[key]+_}. Exemplo:

declare -A xs=([a]=1 [b]="")
test ${xs[a]+_} && echo "a is set"
test ${xs[b]+_} && echo "b is set"
test ${xs[c]+_} && echo "c is set"

Saídas:

a is set
b is set

Para ver como isso funciona, verifique isso .

tokland
fonte
2
O manual de informações recomenda que você use envpara evitar ambiguidades em aliases, progs e outras funções que possam ter adotado o nome "teste". Como acima env test ${xs[a]+_} && echo "a is set". Você também pode obter essa funcionalidade usando colchetes duplos, o mesmo truque e depois verificando se [[ ! -z "${xs[b]+_}" ]] && echo "b is set"
existe
Além disso, você pode usar o ainda mais simples[[ ${xs[b]+set} ]]
Arne L.
5

Existe uma maneira de testar se um elemento de uma matriz associativa existe (não definido), isso é diferente de vazio:

isNotSet() {
    if [[ ! ${!1} && ${!1-_} ]]
    then
        return 1
    fi
}

Então use-o:

declare -A assoc
KEY="key"
isNotSet assoc[${KEY}]
if [ $? -ne 0 ]
then
  echo "${KEY} is not set."
fi
Diego F. Durán
fonte
apenas uma observação: declare -A não funciona no bash 3.2.39 (debian lenny), mas funciona no bash 4.1.5 (debian squeeze)
Paweł Polewicz
Matrizes associativas foram introduzidas no Bash 4.
Diego F. Durán
1
note que if ! some_check then return 1= some_check. Assim: isNotSet() { [[ ... ]] }. Confira minha solução abaixo, você pode fazer isso em uma verificação simples.
tokland
3

Você pode ver se uma entrada está presente canalizando o conteúdo da matriz para grep.

 printf "%s\n" "${mydata[@]}" | grep "^${val}$"

Você também pode obter o índice de uma entrada com grep -n, que retorna o número da linha de uma correspondência (lembre-se de subtrair 1 para obter o índice baseado em zero). Isso será razoavelmente rápido, exceto para matrizes muito grandes.

# given the following data
mydata=(a b c "hello world")

for val in a c hello "hello world"
do
           # get line # of 1st matching entry
    ix=$( printf "%s\n" "${mydata[@]}" | grep -n -m 1 "^${val}$" | cut -d ":" -f1 )

    if [[ -z $ix ]]
    then
        echo $val missing
    else
         # subtract 1.  Bash arrays are zero-based, but grep -n returns 1 for 1st line, not 0 
        echo $val found at $(( ix-1 ))
    fi
done

a found at 0
c found at 2
hello missing
hello world found at 3

explicação:

  • $( ... ) é o mesmo que usar backticks para capturar a saída de um comando em uma variável
  • printf gera mydata um elemento por linha
  • (todas as citações necessárias, além @de *. evitar isso, dividem o "olá mundo" em duas linhas)
  • greppesquisa a string exata: ^e $corresponde ao início e ao fim da linha
  • grep -n retorna a linha #, na forma de 4: hello world
  • grep -m 1 encontra apenas a primeira correspondência
  • cut extrai apenas o número da linha
  • subtraia 1 do número da linha retornada.

Obviamente, você pode dobrar a subtração no comando. Mas, em seguida, teste -1 em falta:

ix=$(( $( printf "%s\n" "${mydata[@]}" | grep -n -m 1 "^${val}$" | cut -d ":" -f1 ) - 1 ))

if [[ $ix == -1 ]]; then echo missing; else ... fi
  • $(( ... )) faz aritmética inteira
kane
fonte
1

Eu não acho que você pode fazê-lo corretamente sem fazer loop, a menos que tenha dados muito limitados na matriz.

Aqui está uma variante simples, isso diria corretamente que "Super User"existe na matriz. Mas também diria que "uper Use"está na matriz.

MyArray=('Super User' 'Stack Overflow' 'Server Fault' 'Jeff' );
FINDME="Super User"

FOUND=`echo ${MyArray[*]} | grep "$FINDME"`

if [ "${FOUND}" != "" ]; then
  echo Array contains: $FINDME
else
  echo $FINDME not found
fi

#
# If you where to add anchors < and > to the data it could work
# This would find "Super User" but not "uper Use"
#

MyArray2=('<Super User>' '<Stack Overflow>' '<Server Fault>' '<Jeff>' );

FOUND=`echo ${MyArray2[*]} | grep "<$FINDME>"`

if [ "${FOUND}" != "" ]; then
  echo Array contains: $FINDME
else
  echo $FINDME not found
fi

O problema é que não há uma maneira fácil de adicionar as âncoras (que eu possa pensar) além de percorrer o array. A menos que você possa adicioná-los antes de colocá-los na matriz ...

Nifle
fonte
É uma boa solução quando as constantes são alfanuméricas (com grep "\b$FINDME\b"). Provavelmente poderia trabalhar com constantes não alfanuméricos que não têm espaços, com "(^| )$FINDME(\$| )"(ou algo assim ... Eu nunca fui capaz de aprender o sabor de usos regexp grep.)
Tgr
1
#!/bin/bash
function in_array {
  ARRAY=$2
  for e in ${ARRAY[*]}
  do
    if [[ "$e" == "$1" ]]
    then
      return 0
    fi
  done
  return 1
}

my_array=(Drupal Wordpress Joomla)
if in_array "Drupal" "${my_array[*]}"
  then
    echo "Found"
  else
    echo "Not found"
fi
Cong Nguyen
fonte
1
Você pode explicar por que está sugerindo essa abordagem? O OP perguntou se existe uma maneira de fazer isso sem fazer um loop pela matriz , que é o que você está fazendo in_array. Cheers
bertieb 27/02
Bem, pelo menos esse loop está encapsulado em uma função, o que pode ser bom o suficiente para muitos casos (com conjuntos de dados pequenos) e não requer o bash 4+. Provavelmente ${ARRAY[@]}deve ser usado.
Tobias